Elasticsearch:构建自动补全功能 - Autocomplete

news/2024/10/23 7:40:12/

什么是自动补全(autocomplete)功能呢?我们举一个很常见的例子。 每当你去谷歌并开始打字时,就会出现一个下拉列表,其中列出了建议。 这些建议与查询相关并帮助用户完成查询。

 Autocomplete 正如维基百科所说的:

Autocomplete 或单词完成是一个功能,应用程序预测使用的其余单词正在键入

它也知道你键入或键入前方搜索。 它通过提示用户在键入文本的可能性和替代方案来帮助他们导航或指导用户。 它减少用户在执行任何搜索操作之前需要输入的字符数量,从而增强用户的搜索体验。

可以通过使用任何数据库来实现 Autocomplete。 在这篇文章中,我们将使用 Elasticsearch 构建自动补全功能。

Elasticsearch 是免费及开发,分布式和基于JSON的搜索引擎,建立在Lucene的顶部。更多关于 Elasticsearch 的介绍,请阅读文章 “Elasticsearch 简介”。

方法

在 Elasticsearch 中,可能有多种构建自动完成功能的方法。 我们将讨论以下方法。

  • Prefix query
  • Edge ngram
  • Completion suggester

Prefiex query

这种方法涉及使用针对自定义字段的前缀查询(prefix query)。 该字段的值可以作为 keyword 存储,因此将多个 terms(单词)存储在一起作为一个术语。 可以使用关键字分词器(keyword tokenizer)来完成这一点。 这种方法遭受了缺点:

  • 由于只支持词首匹配,不能匹配正文中间的 query。
  • 这种类型的查询未针对大型数据集进行优化,可能会导致延迟增加。
  • 由于这是一个查询,因此不会过滤掉重复的结果。 处理此方法的一种解决方法是使用聚合查询对结果进行分组,然后过滤掉结果。 这涉及服务器端的一些处理

Edge Ngrams

有关 edge ngrams 的介绍请参阅之前的文章 “Elasticsearch: Ngrams, edge ngrams, and shingles”。

这种方法涉及在索引和搜索时使用不同的分析器。 索引文档时,可以应用带有 edge n-gram 过滤器的自定义分析器。 在搜索时,可以应用标准分析器。 这可以防止查询被拆分。

Edge N-gram tokeniser 首先将文本分解为自定义字符(空格、特殊字符等)上的单词,然后仅从字符串的开头保留 n-gram。

这种方法也适用于匹配文本中间的查询。 这种方法通常查询速度很快,但可能会导致索引速度变慢和索引存储量变大。

Completion suggester

Elasticsearch 附带一个名为 Completion Suggester 的内部解决方案。 它使用称为有限状态传感器 (Finite State Transducer - FST) 的内存数据结构。 Elasticsearch 以每个段为基础存储 FST,这意味着建议会随着更多新节点的添加而水平扩展。

实施 Completion Suggester 时要记住的一些事情

  • Autosuggest 项应将 completion 类型作为其字段类型。
  • 输入字段可以为单个术语具有各种规范名称或别名。
  • 可以为每个文档定义权重以控制它们的排名。
  • 以小写形式存储所有术语有助于不区分大小写的匹配。
  • 可以启用上下文 suggesters 以支持按特定标准进行过滤或提升。

这种方法是实现自动完成功能的理想方法,但是,它也有一些缺点

  • 匹配总是从文​​本的开头开始。 所以在 movies 数据集中搜索 america 不会产生任何结果。 一种克服方法是在空格上标记输入文本并将所有短语保留为规范名称。 这样 Captain America: Civil War:内战将被存储为:
Captain America: Civil War
America: Civil War
Civil War
War

不支持突出(highlight)显示匹配的词。

没有可用的排序机制。 对建议进行排序的唯一方法是通过权重。 当需要任何自定义排序(如字母排序或按上下文排序)时,这会产生问题。

实现

让我们在 Elasticsearch 中实现上述方法。 我们将使用 movies 数据来构建我们的示例索引。 为了便于参考,我们使用如下的命令来创建 movies 索引:

PUT movies
{"settings": {"index": {"analysis": {"filter": {},"analyzer": {"keyword_analyzer": {"filter": ["lowercase","asciifolding","trim"],"char_filter": [],"type": "custom","tokenizer": "keyword"},"edge_ngram_analyzer": {"filter": ["lowercase"],"tokenizer": "edge_ngram_tokenizer"},"edge_ngram_search_analyzer": {"tokenizer": "lowercase"}},"tokenizer": {"edge_ngram_tokenizer": {"type": "edge_ngram","min_gram": 2,"max_gram": 5,"token_chars": ["letter"]}}}}},"mappings": {"properties": {"name": {"type": "text","fields": {"keywordstring": {"type": "text","analyzer": "keyword_analyzer"},"edgengram": {"type": "text","analyzer": "edge_ngram_analyzer","search_analyzer": "edge_ngram_search_analyzer"},"completion": {"type": "completion"}},"analyzer": "standard"}}}
}

如果我们看到映射,我们会发现 name 是一个 multi-fields 字段,其中包含多个字段,每个字段都以不同的方式进行分析。

  • 使用关键字分词器分析 Fieldname.keywordstring,因此它将用于前缀查询方法
  • 字段 name.edgengram 使用 Edge Ngram 分词器进行分析,因此它将用于 Edge Ngram 方法。
  • Field name.completion 存储为 completion 类型,因此它将用于 Completion Suggester。

我们使用如下命令索引所有的电影:

POST movies/_bulk
{ "index" : {"_id" : "1"} }
{ "name" : "Spider-Man: Homecoming" }
{ "index" : {"_id" : "2"} }
{ "name" : "Ant-man and the Wasp" }
{ "index" : {"_id" : "3"} }
{ "name" : "Avengers: Infinity War Part 2" }
{ "index" : {"_id" : "4"} }
{ "name" : "Captain Marvel" }
{ "index" : {"_id" : "5"} }
{ "name" : "Black Panther" }
{ "index" : {"_id" : "6"} }
{ "name" : "Avengers: Infinity War" }
{ "index" : {"_id" : "7"} }
{ "name" : "Thor: Ragnarok" }
{ "index" : {"_id" : "8"} }
{ "name" : "Guardians of the Galaxy Vol 2" }
{ "index" : {"_id" : "9"} }
{ "name" : "Doctor Strange" }
{ "index" : {"_id" : "10"} }
{ "name" : "Captain America: Civil War" }
{ "index" : {"_id" : "11"} }
{ "name" : "Ant-Man" }
{ "index" : {"_id" : "12"} }
{ "name" : "Avengers: Age of Ultron" }
{ "index" : {"_id" : "13"} }
{ "name" : "Guardians of the Galaxy" }
{ "index" : {"_id" : "14"} }
{ "name" : "Captain America: The Winter Soldier" }
{ "index" : {"_id" : "15"} }
{ "name" : "Thor: The Dark World" }
{ "index" : {"_id" : "16"} }
{ "name" : "Iron Man 3" }
{ "index" : {"_id" : "17"} }
{ "name" : "Marvel’s The Avengers" }
{ "index" : {"_id" : "18"} }
{ "name" : "Captain America: The First Avenger" }
{ "index" : {"_id" : "19"} }
{ "name" : "Thor" }
{ "index" : {"_id" : "20"} }
{ "name" : "Iron Man 2" }
{ "index" : {"_id" : "21"} }
{ "name" : "The Incredible Hulk" }
{ "index" : {"_id" : "22"} }
{ "name" : "Iron Man" }

让我们从 prefix query 方法开始,尝试查找以 th 开头的电影。

查询将是:

GET movies/_search?filter_path=**.hits
{"query": {"prefix": {"name.keywordstring": {"value": "th"}}}
}

查询的结果是:

{"hits": {"hits": [{"_index": "movies","_id": "7","_score": 1,"_source": {"name": "Thor: Ragnarok"}},{"_index": "movies","_id": "15","_score": 1,"_source": {"name": "Thor: The Dark World"}},{"_index": "movies","_id": "19","_score": 1,"_source": {"name": "Thor"}},{"_index": "movies","_id": "21","_score": 1,"_source": {"name": "The Incredible Hulk"}}]}
}

结果是公平的,但是像 Captain America: The Winter SoldierGuardians of the Galaxy 这样的电影被遗漏了,因为前缀查询只匹配文本的开头而不是中间。

让我们尝试寻找另一部以 am 开头的电影。

GET movies/_search?filter_path=**.hits
{"query": {"prefix": {"name.keywordstring": {"value": "am"}}}
}

这里我们没有得到任何结果,尽管 Captain America 满足这个条件。 这就印证了Prefix query 不能用于正文中间匹配的一点。

让我们运行相同的搜索,但使用 Edge Ngram 方法。

GET movies/_search?filter_path=**.hits
{"query": {"match": {"name.edgengram": "am"}}
}

上面运行的结果是:

{"hits": {"hits": [{"_index": "movies","_id": "10","_score": 1.5922177,"_source": {"name": "Captain America: Civil War"}},{"_index": "movies","_id": "14","_score": 1.3930962,"_source": {"name": "Captain America: The Winter Soldier"}},{"_index": "movies","_id": "18","_score": 1.3930962,"_source": {"name": "Captain America: The First Avenger"}}]}
}

让我们再次尝试寻找 Captain America,但这次使用更大的短语 captain america the:

GET movies/_search?filter_path=**.hits
{"query": {"match": {"name.edgengram": "captain america the"}}
}

使用 Edge N-gram 方法,我们得到以下电影:

{"hits": {"hits": [{"_index": "movies","_id": "21","_score": 1.0249562,"_source": {"name": "The Incredible Hulk"}},{"_index": "movies","_id": "17","_score": 0.9822227,"_source": {"name": "Marvel’s The Avengers"}},{"_index": "movies","_id": "2","_score": 0.94290996,"_source": {"name": "Ant-man and the Wasp"}},{"_index": "movies","_id": "13","_score": 0.94290996,"_source": {"name": "Guardians of the Galaxy"}},{"_index": "movies","_id": "15","_score": 0.906623,"_source": {"name": "Thor: The Dark World"}},{"_index": "movies","_id": "8","_score": 0.8730254,"_source": {"name": "Guardians of the Galaxy Vol 2"}},{"_index": "movies","_id": "14","_score": 0.7365507,"_source": {"name": "Captain America: The Winter Soldier"}},{"_index": "movies","_id": "18","_score": 0.7365507,"_source": {"name": "Captain America: The First Avenger"}}]}
}

如果我们观察我们的短语,只有两个建议是有意义的。 匹配这么多术语的原因是 match 子句的功能。 匹配包括所有包含 captain OR america OR the 的文件。 由于该字段是使用 ngram 分析的,因此也会包含更多建议(如果存在)。

让我们尝试使用针对相同短语 captain america the 的 suggest 查询。 建议查询的编写方式略有不同。

GET movies/_search?filter_path=suggest
{"suggest": {"movie-suggest": {"prefix": "captain america the","completion": {"field": "name.completion"}}}
}

结果我们得到以下电影:

{"suggest": {"movie-suggest": [{"text": "captain america the","offset": 0,"length": 19,"options": [{"text": "Captain America: The First Avenger","_index": "movies","_id": "18","_score": 1,"_source": {"name": "Captain America: The First Avenger"}},{"text": "Captain America: The Winter Soldier","_index": "movies","_id": "14","_score": 1,"_source": {"name": "Captain America: The Winter Soldier"}}]}]}
}

让我们尝试相同的查询,但这次使用了错别字 captain america the。

GET movies/_search?filter_path=suggest
{"suggest": {"movie-suggest": {"prefix": "captain amrica the","completion": {"field": "name.completion"}}}
}

上面的电影建议没有返回结果,因为不支持模糊性。 我们可以通过以下方式更新查询以包含对模糊性的支持:

GET movies/_search?filter_path=suggest
{"suggest": {"movie-suggest": {"prefix": "captain amrica the","completion": {"field": "name.completion","fuzzy": {"fuzziness": 1}}}}
}

以上查询返回以下结果:

{"suggest": {"movie-suggest": [{"text": "captain amrica the","offset": 0,"length": 18,"options": [{"text": "Captain America: The First Avenger","_index": "movies","_id": "18","_score": 10,"_source": {"name": "Captain America: The First Avenger"}},{"text": "Captain America: The Winter Soldier","_index": "movies","_id": "14","_score": 10,"_source": {"name": "Captain America: The Winter Soldier"}}]}]}
}

结论

可以使用多种方法在 ElasticSearch 中实现自动完成功能。 Completion Suggester 涵盖了实现功能齐全且快速的自动完成所需的大多数情况。


http://www.ppmy.cn/news/29931.html

相关文章

基于支持向量机SVM的面部表情分类预测

目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 Libsvm工具箱详解 简介 参数说明 易错及常见问题 SVM应用实例,基于SVM的面部表情分类预测 代码 结果分析 展望 支持向量机SVM的详细原理 SVM的定义 支持向量机(support vector machines, SVM)是一种二分类模型,它的基本…

【机器学习面试】百面机器学习笔记和问题总结+扩展面试题

第1章 特征工程 1、为什么需要对数值类型的特征做归一化? (1)消除量纲,将所有特征统一到一个大致相同的区间范围,使不同指标之间具由可比性; (2)可以加快梯度下降收敛的速度&#…

【JavaSE】数组的定义和使用(上)

数组的定义和使用(上)6-数组的定义与使用1. 数组的基本概念1.1 为什么要使用数组1.2 什么是数组1.3 数组的创建及初始化1.3.1 数组的创建1.3.2 数组的初始化1.4 数组的使用1.4.1 数组中元素的访问1.4.2 遍历数组2. 数组是引用类型2.1 初始JVM的内存分布2…

Zookeeper3.5.7版本——选举机制(非第一次启动)

目录一、ZooKeeper集群中哪些情况会进入Leader选举二、当一台机器进入Leader选举流程时,当前集群的两种状态2.1、集群中本来就已经存在一个Leader2.2、集群中确实不存在Leader三、Zookeeper中的一些概念了解3.1、SID3.2、ZXID3.3、Epoch一、ZooKeeper集群中哪些情况…

【Python】使用Playwright断言方法验证网页和Web应用程序状态

作为测试框架,Playwright 提供了一系列断言方法,您可以使用它们来验证网页和 Web 应用程序的状态。在这篇博客中,田辛老师将介绍 Playwright 中可用的各种断言方法,并为每种方法提供示例。 assert page.url() expected_url &…

WSL2使用Nvidia-Docker实现深度学习环境自由部署

1. Win11 显卡驱动的安装 注意:WSL2中是不需要且不能安装任何显卡驱动的,它的显卡驱动完全依赖于 Win11 中的显卡驱动,因此我们只需要安装你显卡对应的 Win11 版本显卡驱动版本(必须是 Win11 版本的驱动),…

phpinfo包含临时文件Getshell全过程及源码

目录 前言 原理 漏洞复现 靶场环境 源码 复现过程 前言 PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如session文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服…

湖州银行冲刺A股上市:计划募资约24亿元,资产质量水平较高

3月4日,湖州银行股份有限公司(下称“湖州银行”)递交招股书,准备在上海证券交易所主板上市。本次冲刺上市,湖州银行计划募资23.98亿元,将在扣除发行费用后全部用于补充该行资本金。 湖州银行在招股书中表示…