掌握 analyze API,一举搞定 Elasticsearch 分词难题
初次接触 Elasticsearch 的同学经常会遇到分词相关的难题,比如如下这些场景:
- 为什么明明有包含搜索关键词的文档,但结果里面就没有相关文档呢?
- 我存进去的文档到底被分成哪些词(term)了?
- 我得自定义分词规则,但感觉好麻烦呢,无从下手
如果你遇到过类似的问题,希望本文可以解决你的疑惑。
1. 上手
让我们从一个实例出发,如下创建一个文档:
PUT test/doc/1
{
"msg":"Eating an apple a day keeps doctor away"
}
然后我们做一个查询,我们试图通过搜索 eat
这个关键词来搜索这个文档
POST test/_search
{
"query":{
"match":{
"msg":"eat"
}
}
}
ES的返回结果为0。这不太对啊,我们用最基本的字符串查找也应该能匹配到上面新建的文档才对啊!
各位不要急,我们先来看看什么是分词。
2. 分词
搜索引擎的核心是倒排索引(这里不展开讲),而倒排索引的基础就是分词。所谓分词可以简单理解为将一个完整的句子切割为一个个单词的过程。在 es 中单词对应英文为 term
。我们简单看个例子:
ES 的倒排索引即是根据分词后的单词创建,即 我
、爱
、北京
、天安门
这4个单词。这也意味着你在搜索的时候也只能搜索这4个单词才能命中该文档。
实际上 ES 的分词不仅仅发生在文档创建的时候,也发生在搜索的时候,如下图所示:
读时分词
发生在用户查询时,ES 会即时地对用户输入的关键词进行分词,分词结果只存在内存中,当查询结束时,分词结果也会随即消失。而写时分词
发生在文档写入时,ES 会对文档进行分词后,将结果存入倒排索引,该部分最终会以文件的形式存储于磁盘上,不会因查询结束或者 ES 重启而丢失。
ES 中处理分词的部分被称作分词器,英文是Analyzer
,它决定了分词的规则。ES 自带了很多默认的分词器,比如Standard
、 Keyword
、Whitespace
等等,默认是 Standard
。当我们在读时或者写时分词时可以指定要使用的分词器。
3. 写时分词结果
回到上手阶段,我们来看下写入的文档最终分词结果是什么。通过如下 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"
}
}
}
4. 读时分词结果
由于读时分词器默认与写时分词器默认保持一致,拿 上手 中的例子,你搜索 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
分词器,它没有做任何处理。
5. 解释问题
现在我们再来看下 上手 中所遇问题的解决思路。
- 查看文档写时分词结果
- 查看查询关键词的读时分词结果
- 匹对两者是否有命中
我们简单分析如下:
由上图可以定位问题的原因了。
6. 解决需求
由于 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
肯定都可以匹配对应的文档了。至此,需求解决。
7. 深入分析
最后我们来看下为什么english
分词器可以解决我们遇到的问题。一个分词器由三部分组成:char filter、tokenizer 和 token filter。各部分的作用我们这里就不展开了,我们来看下 standard
和english
分词器的区别。
从上图可以看出,english
分词器在 Token Filter 中和 Standard
不同,而发挥主要作用的就是 stemmer
,感兴趣的同学可以自行去看起它的作用。
8. 自定义分词
如果我们不使用 english
分词器,自定义一个分词器来实现上述需求也是完全可行的,这里不详细讲解了,只给大家讲一个快速验证自定义分词器效果的方法,如下:
POST _analyze
{
"char_filter": [],
"tokenizer": "standard",
"filter": [
"stop",
"lowercase",
"stemmer"
],
"text": "Eating an apple a day keeps doctor away"
}
通过上面的 api 你可以快速验证自己要定制的分词器,当达到自己需求后,再将这一部分配置加入索引的配置。
至此,我们再看开篇的三个问题,相信你已经心里有答案了,赶紧上手去自行测试下吧!
原文地址:https://elasticsearch.cn/article/771
掌握 analyze API,一举搞定 Elasticsearch 分词难题的更多相关文章
- 不花钱搞定PDF编辑难题
PDF格式是专为显示而设计的格式,并不容易被编辑,市面上并没有一款可以真正免费使用的PDF编辑器. 不花钱搞定PDF编辑难题的办法: 1.免费使用PDF编辑器+去水印:免费版的PDF编辑器不是会加水印 ...
- Elasticsearch JAVA api轻松搞定groupBy聚合
本文给出如何使用Elasticsearch的Java API做类似SQL的group by聚合. 为了简单起见,只给出一级groupby即group by field1(而不涉及到多级,例如group ...
- 搞定 ElasticSearch系列一 下载安装
一.安装jdk 二.安装ElasticSearch 1.ElasticSearch下载地址: 2: 配置ElasticSearch 3:启动ElasticSearch 4: 安装ElasticSear ...
- 一篇文章带你搞定 ElasticSearch 术语
这篇文章主要介绍 ElasticSearch 的基本概念,学习文档.索引.集群.节点.分片等概念,同时会将 ElasticSearch 和关系型数据库做简单的类比,还会简单介绍 REST API 的使 ...
- 教你搞定ElasticSearch(head)
简介: ElasticSearch(以下简称ES)是一个基于Lucene构建的开源(open-source),分布式(distributed),RESTful,实时(real-time)的搜索与分析( ...
- 【攻略】百度货币识别API,搞定防诈骗的应用小程序
1.需求及方案: 近两年用外币进行诈骗的案件很多.例如:2015年12月,一安徽诈骗团伙,用不值1角人民币的50印蒂(intis,秘鲁旧货币,1991年发行新货币后已停止流通,目前无货币价值,仅有&q ...
- 一文搞懂 Elasticsearch 之 Mapping
这篇文章主要介绍 Mapping.Dynamic Mapping 以及 ElasticSearch 是如何自动判断字段的类型,同时介绍 Mapping 的相关参数设置. 首先来看下什么是 Mappin ...
- 一个共通的viewModel搞定所有的分页查询一览及数据导出(easyui + knockoutjs + mvc4.0)
前言 大家看标题就明白了我想写什么了,在做企业信息化系统中可能大家写的最多的一种页面就是查询页面了.其实每个查询页面,除了条件不太一样,数据不太一样,其它的其实都差不多.所以我就想提取一些共通的东西出 ...
- 【高德地图API】一句话搞定webmap(一)——轻地图组件
原文:[高德地图API]一句话搞定webmap(一)——轻地图组件 摘要: 遥想当年,在APP中加入LBS元素相当困难:要刻苦学习java,要刻苦学习iOS开发,要刻苦学习javascript…… 而 ...
随机推荐
- Spring Boot教程(三十二)多数据源配置与使用(1)
之前在介绍使用JdbcTemplate和Spring-data-jpa时,都使用了单数据源.在单数据源的情况下,Spring Boot的配置非常简单,只需要在application.propertie ...
- Ubuntu18.04安装rabbitvcs svn图形化客户端和简单实用
1.1 自带source源里面查找rabbitvcs信息 sudo apt search rabbitvcs 1.2 安装rabbitvcs sudo apt install rabbitvcs- ...
- rem等比例自适应手机尺寸
方法:用sass的函数动态计算rem值 $rem : 75px;基准值 设计图是750的宽 设为$rem变量设为75,设计图是350的宽 设为$rem变量设为35,老的写法 需要用js来配合来动态改变 ...
- Redis的一点笔记
Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 优势: 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s . 丰富 ...
- @Transient使用心得
使用注解@Transient使表中没有此字段 注意,实体类中要使用org.springframework.data.annotation.Transient 在写实体类时发现有加@Transient注 ...
- Understand RNN with TensorFlow in 7 Steps
待翻译 https://medium.com/@erikhallstrm/hello-world-rnn-83cd7105b767
- Spring的jsp标签库
1,主要有两个 一个用于渲染HTML表单标签, 这些标签会绑定model中的某个属性 另一个包换了一些工具类标签 2,将表单绑定到模型上 包含了14个标签,海报一个人为用户展现错误的标签,它会将错误信 ...
- Mybaits 运行原理流程图
- Navicat1_介绍
https://study.163.com/course/courseMain.htm?courseId=1006383008&share=2&shareId=400000000398 ...
- golang中,unsafe.sizeof到底是干嘛的?
https://www.golangtc.com/t/5ad833404ce40d2654053485 小生初学Go,有一点不懂,今天为了知道空结构体到底占多大的空间的时候,去百度说用unsafe.s ...