在ElasticSearch中搜索具有相关时间戳的成绩单

4bbkushb  于 2023-10-17  发布在  ElasticSearch
关注(0)|答案(1)|浏览(130)

我有这样的文件:

{
  "title": "Hello, World!",
  "segments": [
    {
      "text": "waive such protections",
      "start": 0,
      "end": 7040
    },
    {
      "text": "in all contexts",
      "start": 7040,
      "end": 8500
    }
  ]
}

这个例子代表了一个完整句子的结尾:我遇到的问题是,我可以搜索“放弃这种保护”或“在所有上下文中”,但我不能搜索“在所有上下文中放弃这种保护“。

是否有一种方法可以进行短语搜索,并允许短语搜索跨越segments数组中的多个相邻条目?
**或者,有没有更好的方法可以构建我的文档?我的目标是能够搜索短语,并返回在成绩单中找到该短语的时间戳(这些文档是从vtt文件生成的,但我可以将它们更改为任何我需要的形状)。

现在我正在对查询的所有连续子集进行多个短语搜索。本质上,bool查询将minimum_should_match设置为1should设置为短语,搜索waivewaive suchwaive such protectionswaive such protections in等。“在所有情况下放弃这种保护”的查询实际上变成了35个查询。这是可行的,但非常缓慢。

wswtfjt7

wswtfjt71#

有没有一种方法可以进行短语搜索,并允许短语搜索跨越segments数组中的多个相邻条目?
不幸的是,首先Elasticsearch将这些arrays(除非它们是nested)转换为单个文档,然后转换为唯一的单词列表。在这一点上,所有关于相邻字段的信息都丢失了。Elasticsearch documentation更详细:
当一个文档被存储时,它被索引并在near real-time中完全搜索-在1秒内。Elasticsearch使用一种称为反向索引的数据结构,它支持非常快速的全文搜索。倒排索引列出任何文档中出现的每个唯一单词,并标识每个单词出现的所有文档。
正如您所指出的,您可以尝试慢慢地构建一系列查询,这些查询可以找到可能发生拆分的所有可能方式。我建议使用Regexp query至少确保字段中的单词位于开头或结尾,但不支持锚运算符:
Lucene的正则表达式引擎不支持锚操作符,例如^(行首)或$(行尾)。

选项一

一个可以改进的想法是简单地添加一个连接相邻text字段的字段:

"segments": [
    {
      "text": "waive such protections",
      "text_follows": "waive such protections in all contexts",
      "start": 0,
      "end": 7040
    },
    {
      "text": "in all contexts",
      "text_follows": "",
      "start": 7040,
      "end": 8500
    }
]

然后,您可以执行一个简单的match phrase query,并删除对这35个查询的需要。

"query": {
  "match_phrase": {
    "segments.text_follows": "waive such protections in all contexts"
  }
}

这确实会增加索引的大小,并且匹配短语的最大长度在创建索引时设置。
另一件需要注意的事情是,即使匹配了一个数组项,Elasticsearch仍然会返回整个数组。这可以快速增加HTTP响应的大小。为了解决这个问题,可以考虑使用nested arrays,它将数组的每个元素拆分为单独的文档。

PUT my-index
{
  "mappings": {
    "properties": {
      "segments": {
        "type": "nested"
      }
    }
  }
}

然后可以使用inner hits检索这些文档:

GET my-index/_search
{
  "_source": false,
  "query": {
    "nested": {
      "path": "segments",
      "query": {
        "match_phrase": {
          "segments.text_follows": "such protections in"
        }
      },
      "inner_hits": {}
    }
  }
}

然后,您将需要在客户端删除原始text字段未以搜索短语的开头结束的任何结果。

...
    "hits": [
      {
        "_index": "my-index",
        "_id": "1",
        "_nested": {
          "field": "segments",
          "offset": 0
        },
        "_score": 0.8630463,
        "_source": {
          "text": "waive such protections",
          "text_follows": "waive such protections in all contexts",
          "start": 0,
          "end": 7040
        }
      }
    ]
...

选项二

但是,对于更复杂或更长的match_phrase查询,前面的选项不能很好地扩展,特别是如果您希望允许用户使用simple query string query创建自己的搜索词。
为了解决这个问题,你需要将所有的文本数据连接到一个文档中的一个字段中,并直接在每个单词旁边编码时间戳,然后通过pattern replace character filter删除,以便在搜索时忽略数据。

PUT my-index
{
  "mappings": {
    "properties": {
      "title": {"type": "text"},
      "text": {"type": "text", "analyzer": "my_analyzer"}
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "char_filter": [
            "my_char_filter"
          ]
        }
      },
      "char_filter": {
        "my_char_filter": {
          "type": "pattern_replace",
          "pattern": "\\[[0-9]+\\]",
          "replacement": ""
        }
      }
    }
  }
}

PUT my-index/_doc/1
{
  "title": "Hello, World!",
  "text": "waive[0] such[0] protections[0] in[7040] all[7040] contexts[7040]"
}

然后,您将需要依靠内置的荧光笔来提取文本的相关部分。

GET my-index/_search
{
  "_source": false,
  "query": {
    "match_phrase": {
      "text": "such protections in"
    }
  },
  "highlight": {
    "fields": {
      "text": {}
    }
  }
}

然后,需要在客户端处理命中,以提取时间戳数据:

...
    "hits": [
      {
        "_index": "my-index",
        "_id": "1",
        "_score": 0.8630463,
        "highlight": {
          "text": [
            "waive[0] <em>such[0] protections[0] in[7040]</em> all[7040] contexts[7040]"
          ]
        }
      }
    ]
...

如果你不担心空间,你也可以包括结束时间戳(例如,waive[0-7040])并调整char_filter中的正则表达式
希望有帮助

相关问题