初次接触 Elasticsearch 的同学经常会遇到分词相关的难题,比如如下这些场景:

  1. 为什么明明有包含搜索关键词的文档,但结果里面就没有相关文档呢?
  2. 我存进去的文档到底被分成哪些词(term)了?
  3. 我得自定义分词规则,但感觉好麻烦呢,无从下手

如果你遇到过类似的问题,希望本文可以解决你的疑惑。

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 自带了很多默认的分词器,比如StandardKeywordWhitespace等等,默认是 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. 解释问题

现在我们再来看下 上手 中所遇问题的解决思路。

  1. 查看文档写时分词结果
  2. 查看查询关键词的读时分词结果
  3. 匹对两者是否有命中

我们简单分析如下:

由上图可以定位问题的原因了。

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。各部分的作用我们这里就不展开了,我们来看下 standardenglish分词器的区别。

从上图可以看出,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 分词难题的更多相关文章

  1. 不花钱搞定PDF编辑难题

    PDF格式是专为显示而设计的格式,并不容易被编辑,市面上并没有一款可以真正免费使用的PDF编辑器. 不花钱搞定PDF编辑难题的办法: 1.免费使用PDF编辑器+去水印:免费版的PDF编辑器不是会加水印 ...

  2. Elasticsearch JAVA api轻松搞定groupBy聚合

    本文给出如何使用Elasticsearch的Java API做类似SQL的group by聚合. 为了简单起见,只给出一级groupby即group by field1(而不涉及到多级,例如group ...

  3. 搞定 ElasticSearch系列一 下载安装

    一.安装jdk 二.安装ElasticSearch 1.ElasticSearch下载地址: 2: 配置ElasticSearch 3:启动ElasticSearch 4: 安装ElasticSear ...

  4. 一篇文章带你搞定 ElasticSearch 术语

    这篇文章主要介绍 ElasticSearch 的基本概念,学习文档.索引.集群.节点.分片等概念,同时会将 ElasticSearch 和关系型数据库做简单的类比,还会简单介绍 REST API 的使 ...

  5. 教你搞定ElasticSearch(head)

    简介: ElasticSearch(以下简称ES)是一个基于Lucene构建的开源(open-source),分布式(distributed),RESTful,实时(real-time)的搜索与分析( ...

  6. 【攻略】百度货币识别API,搞定防诈骗的应用小程序

    1.需求及方案: 近两年用外币进行诈骗的案件很多.例如:2015年12月,一安徽诈骗团伙,用不值1角人民币的50印蒂(intis,秘鲁旧货币,1991年发行新货币后已停止流通,目前无货币价值,仅有&q ...

  7. 一文搞懂 Elasticsearch 之 Mapping

    这篇文章主要介绍 Mapping.Dynamic Mapping 以及 ElasticSearch 是如何自动判断字段的类型,同时介绍 Mapping 的相关参数设置. 首先来看下什么是 Mappin ...

  8. 一个共通的viewModel搞定所有的分页查询一览及数据导出(easyui + knockoutjs + mvc4.0)

    前言 大家看标题就明白了我想写什么了,在做企业信息化系统中可能大家写的最多的一种页面就是查询页面了.其实每个查询页面,除了条件不太一样,数据不太一样,其它的其实都差不多.所以我就想提取一些共通的东西出 ...

  9. 【高德地图API】一句话搞定webmap(一)——轻地图组件

    原文:[高德地图API]一句话搞定webmap(一)——轻地图组件 摘要: 遥想当年,在APP中加入LBS元素相当困难:要刻苦学习java,要刻苦学习iOS开发,要刻苦学习javascript…… 而 ...

随机推荐

  1. 拉格朗日插值法板子(dls)

    namespace polysum { ; ll a[D],f[D],g[D],p[D],p1[D],p2[D],b[D],h[D][],C[D]; ll calcn(int d,ll *a,ll n ...

  2. [CSP-S模拟测试]:组合(欧拉路)

    题目传送门(内部题119) 输入格式 第一行,三个整数$T,M,N$. 接下来的$N$行,每行两个整数$u_i,v_i$($i$从$1$开始编号).允许$u_i=v_i$,也允许同样的简单词多次出现. ...

  3. uni-app tabBar 踩坑

    { "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages { "pa ...

  4. ubuntu 16.04 安装最新的 docker

      转载地址:https://www.cnblogs.com/tianhei/p/7802064.html 本文将介绍在ubuntu16.04系统下安装和升级docker.docker-compose ...

  5. EBS 清除高速缓存

    以R12.1.3为例: 以 “功能管理员 ”职责打开OAF界面 然后依次点击“核心服务”->“高速缓存结构”->“全局配置”->“清除所有高速缓存”->“是”,即可

  6. React 中 refs 的作用是什么?

    Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄.我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回 ...

  7. Android Context完全解析与各种获取Context方法

    Context类型 我们知道,Android应用都是使用Java语言来编写的,那么大家可以思考一下,一个Android程序和一个Java程序,他们最大的区别在哪里?划分界限又是什么呢?其实简单点分析, ...

  8. leetcode 29两数相除

    我理解本题是考察基于加减实现除法,代码如下: class Solution { public: //只用加减号实现除法, //不用加减号实现除法: int divide(int dividend, i ...

  9. 开源控件slidingmenu的使用

    在github.com网站搜索slidingmenu后https://github.com/jfeinstein10/SlidingMenu 下载demo,导入library到你的项目中,添加到你项目 ...

  10. Ruby小白入门笔记之 <Gemfile 文件>

    因为初学Ruby,四处查资料无果,才来的贴出亲自试过的操作,覆盖整个个人入门笔记博客中,故所有的操作,都以最明了的方式阐述,当你创建完一个新的Rails应用后,你发现JAVA中我们可以编写maven聚 ...