初次使用 Elasticsearch 遇多种分词难题?那是你没掌握这些原理
作者介绍
魏彬,普翔科技 CTO,开源软件爱好者,中国第一位 Elastic 认证工程师,《Elastic日报》和 《ElasticTalk》社区项目发起人,被 elastic 中国公司授予 2019 年度合作伙伴架构师特别贡献奖。对 Elasticsearch、Kibana、Beats、Logstash、Grafana 等开源软件有丰富的实践经验,为零售、金融、保险、证券、科技等众多行业的客户提供过咨询和培训服务,帮助客户在实际业务中找准开源软件的定位,实现从 0 到 1 的落地、从 1 到 N 的拓展,产生实际的业务价值。
初次接触 Elasticsearch 的同学经常会遇到分词相关的难题,比如如下这些场景:
1、为什么命名有包含搜索关键词的文档,但结果里面就没有相关文档呢?
2、我存进去的文档到底被分成哪些词(term)了?
3、我得自定义分词规则,但感觉好麻烦呢,无从下手
如果你遇到过类似的问题,希望本文可以解决你的疑惑。
一、上手
让我们从一个实例出发,如下创建一个文档:
PUT test/doc/1 { "msg":"Eating an apple a day keeps doctor away" }
然后我们做一个查询,我们试图通过搜索 eat这个关键词来搜索这个文档
POST test/_search { "query":{ "match":{ "msg":"eat" } } }
ES的返回结果为0。这不太对啊,我们用最基本的字符串查找也应该能匹配到上面新建的文档才对啊!
各位不要急,我们先来看看什么是分词。
二、 分词
搜索引擎的核心是倒排索引(这里不展开讲),而倒排索引的基础就是分词。所谓分词可以简单理解为将一个完整的句子切割为一个个单词的过程。在 es 中单词对应英文为 term。我们简单看个例子:
ES 的倒排索引即是根据分词后的单词创建,即 我、爱、北京、天安门这4个单词。这也意味着你在搜索的时候也只能搜索这4个单词才能命中该文档。
实际上 ES 的分词不仅仅发生在文档创建的时候,也发生在搜索的时候,如下图所示:
读时分词发生在用户查询时,ES 会即时地对用户输入的关键词进行分词,分词结果只存在内存中,当查询结束时,分词结果也会随即消失。而写时分词发生在文档写入时,ES 会对文档进行分词后,将结果存入倒排索引,该部分最终会以文件的形式存储于磁盘上,不会因查询结束或者 ES 重启而丢失。
ES 中处理分词的部分被称作分词器,英文是Analyzer,它决定了分词的规则。ES 自带了很多默认的分词器,比如Standard、 Keyword、Whitespace等等,默认是 Standard。当我们在读时或者写时分词时可以指定要使用的分词器。
三、写时分词结果
回到上手阶段,我们来看下写入的文档最终分词结果是什么。通过如下 api 可以查看:
POST test/_analyze { "field": "msg", "text": "Eating an apple a day keeps doctor away" }
其中 test为索引名,_analyze 为查看分词结果的 endpoint,请求体中 field 为要查看的字段名,text为具体值。该 api 的作用就是请告诉我在 test 索引使用 msg 字段存储一段文本时,es 会如何分词。
返回结果如下:
{ "tokens": [ { "token": "eating", "start_offset": 0, "end_offset": 6, "type": "<ALPHANUM>", "position": 0 }, { "token": "an", "start_offset": 7, "end_offset": 9, "type": "<ALPHANUM>", "position": 1 }, { "token": "apple", "start_offset": 10, "end_offset": 15, "type": "<ALPHANUM>", "position": 2 }, { "token": "a", "start_offset": 16, "end_offset": 17, "type": "<ALPHANUM>", "position": 3 }, { "token": "day", "start_offset": 18, "end_offset": 21, "type": "<ALPHANUM>", "position": 4 }, { "token": "keeps", "start_offset": 22, "end_offset": 27, "type": "<ALPHANUM>", "position": 5 }, { "token": "doctor", "start_offset": 28, "end_offset": 34, "type": "<ALPHANUM>", "position": 6 }, { "token": "away", "start_offset": 35, "end_offset": 39, "type": "<ALPHANUM>", "position": 7 } ] }
返回结果中的每一个 token即为分词后的每一个单词,我们可以看到这里是没有 eat 这个单词的,这也解释了在上手中我们搜索 eat 没有结果的情况。如果你去搜索 eating ,会有结果返回。
写时分词器需要在 mapping 中指定,而且一经指定就不能再修改,若要修改必须新建索引。如下所示我们新建一个名为ms_english 的字段,指定其分词器为 english:
PUT test/_mapping/doc { "properties": { "msg_english":{ "type":"text", "analyzer": "english" } } }
四、读时分词结果
由于读时分词器默认与写时分词器默认保持一致,拿 上手 中的例子,你搜索 msg 字段,那么读时分词器为 Standard ,搜索 msg_english 时分词器则为 english。这种默认设定也是非常容易理解的,读写采用一致的分词器,才能尽最大可能保证分词的结果是可以匹配的。
然后 ES 允许读时分词器单独设置,如下所示:
POST test/_search { "query":{ "match":{ "msg":{ "query": "eating", "analyzer": "english" } } } }
如上 analyzer 字段即可以自定义读时分词器,一般来讲不需要特别指定读时分词器。
如果不单独设置分词器,那么读时分词器的验证方法与写时一致;如果是自定义分词器,那么可以使用如下的 api 来自行验证结果。
POST _analyze { "text":"eating", "analyzer":"english" }
返回结果如下:
{ "tokens": [ { "token": "eat", "start_offset": 0, "end_offset": 6, "type": "<ALPHANUM>", "position": 0 } ] }
由上可知 english分词器会将 eating处理为 eat,大家可以再测试下默认的 standard分词器,它没有做任何处理。
五、解释问题
现在我们再来看下 上手 中所遇问题的解决思路。
1、查看文档写时分词结果
2、查看查询关键词的读时分词结果
3、匹对两者是否有命中
我们简单分析如下:
由上图可以定位问题的原因了。
六、解决需求
由于 eating只是 eat的一个变形,我们依然希望输入 eat时可以匹配包含 eating的文档,那么该如何解决呢?
答案很简单,既然原因是在分词结果不匹配,那么我们就换一个分词器呗~ 我们可以先试下 ES 自带的 english分词器,如下:
# 增加字段 msg_english,与 msg 做对比 PUT test/_mapping/doc { "properties": { "msg_english":{ "type":"text", "analyzer": "english" } } } # 写入相同文档 PUT test/doc/1 { "msg":"Eating an apple a day keeps doctor away", "msg_english":"Eating an apple a day keeps doctor away" } # 搜索 msg_english 字段 POST test/_search { "query": { "match": { "msg_english": "eat" } } }
执行上面的内容,我们会发现结果有内容了,原因也很简单,如下图所示:
由上图可见 english分词器会将 eating分词为 eat,此时我们搜索 eat或者 eating肯定都可以匹配对应的文档了。至此,需求解决。
七、深入分析
最后我们来看下为什么english分词器可以解决我们遇到的问题。一个分词器由三部分组成:char filter、tokenizer 和 token filter。各部分的作用我们这里就不展开了,我们来看下 standard和english分词器的区别。
从上图可以看出,english分词器在 Token Filter 中和 Standard不同,而发挥主要作用的就是 stemmer,感兴趣的同学可以自行去看起它的作用。
八、自定义分词
如果我们不使用 english分词器,自定义一个分词器来实现上述需求也是完全可行的,这里不详细讲解了,只给大家讲一个快速验证自定义分词器效果的方法,如下:
POST _analyze { "char_filter": [], "tokenizer": "standard", "filter": [ "stop", "lowercase", "stemmer" ], "text": "Eating an apple a day keeps doctor away" }
通过上面的 api 你可以快速验证自己要定制的分词器,当达到自己需求后,再将这一部分配置加入索引的配置。
至此,我们再看开篇的三个问题,相信你已经心里有答案了,赶紧上手去自行测试下吧!
声明:本文由原文《掌握 analyze API,一举搞定 Elasticsearch 分词难题》作者“魏彬”授权转载,对未经许可擅自使用者,保留追究其法律责任的权利。
【阿里云Elastic Stack】100%兼容开源ES,独有9大能力,提供免费X-pack服务(单节点价值$6000)
相关活动
更多折扣活动,请访问阿里云 Elasticsearch 官网
阿里云 Elasticsearch 商业通用版,1核2G ,SSD 20G首月免费
阿里云 Logstash 2核4G首月免费

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Apache Spark 3.0 中的向量化 IO
本文转载自:过往记忆大数据原文链接 R 是数据科学中最流行的计算机语言之一,专门用于统计分析和一些扩展,如用于数据处理和机器学习任务的 RStudio addins 和其他 R 包。此外,它使数据科学家能够轻松地可视化他们的数据集。 通过在 Apache Spark 中使用 SparkR,可以很容易地扩展 R 代码。要交互式地运行作业,可以通过运行 R shell 轻松地在分布式集群中运行 R 的作业。 当 SparkR 不需要与 R 进程交互时,其性能实际上与 Scala、Java 和 Python 等其他语言 API 相同。但是,当 SparkR 作业与本机 R 函数或数据类型交互时,会性能显著下降。 如果在 Spark 和 R 之间使用 Apache Arrow 来进行数据交换,其性能会有很大的提升。这篇博客文章概述了 SparkR 中 Spark 和 R 的交互,并对比了没有向量化执行和有向量化执行的性能差异。 Spark 和 R 交互 SparkR 不仅支持丰富的 ML 和类似 SQL 的 API 集合,而且还支持用于直接与 R 代码进行交互的一组 API。例如,Spark ...
- 下一篇
Amazon Redshift数据迁移阿里云MaxCompute技术方案
1 方案概述 本文将介绍如何通过公网环境迁移Amazon Redshift数据到阿里云MaxCompute服务当中。 本方案的总体迁移流程如下: 第一, 将Amazon Redshift 中的数据导出到Amazon S3存储上; 第二, 借助阿里云在线迁移服务(Data Online Migration)从AWS S3将数据迁移到阿里云OSS上; 第三, 将数据从阿里云OSS加载到同Region的MaxCompute项目中,并进行校验数据完整性。 2 前提条件 · 准备Redshift的集群环境及数据环境; 使用已有的Redshift集群或创建新的Redshift集群: **** 在Redshift集群中准备好需要迁移的数据,本方案中在public schema中准备了TPC-H数据集进行方案介绍: · 准备MaxCompute的项目环境; 在阿里云Region创建MaxCompute项目,这里以新加坡Region为例,创建了作为迁移目标的MaxCompute项目: 3 Redshift导出数据到S3 3.1 Unload简介 3.1.1 命令介绍 AWS Redshift支持Role...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- MySQL8.0.19开启GTID主从同步CentOS8
- Red5直播服务器,属于Java语言的直播服务器
- Mario游戏-低调大师作品
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题