ES内置的Analyzer分析器

es自带了许多内置的Analyzer分析器,无需配置就可以直接在index中使用:

  • 标准分词器(standard):以单词边界切分字符串为terms,根据Unicode文本分割算法。它会移除大部分的标点符号,小写分词后的term,支持停用词。
  • 简单分词器(simple):该分词器会在遇到非字母时切分字符串,小写所有的term。
  • 空格分词器(whitespace):遇到空格字符时切分字符串,
  • 停用词分词器(stop):类似简单分词器,同时支持移除停用词。
  • 关键词分词器(keyword):无操作分词器,会输出与输入相同的内容作为一个single term。
  • 模式分词器(pattern):使用正则表达式讲字符串且分为terms。支持小写字母和停用词。
  • 语言分词器(language):支持许多基于特定语言的分词器,比如english或french。
  • 签名分词器(fingerprint):是一个专家分词器,会产生一个签名,可以用于去重检测。
  • 自定义分词器:如果内置分词器无法满足你的需求,可以自定义custom分词器,根据不同的character filters,tokenizer,token filters的组合 。例如IK就是自定义分词器。
  • word_delimiter:将一个单词再拆成子分词

详见文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html

拼音分词器

1. 在线安装

1
2
3
4
5
6
7
8
9
10
# 进入容器内部
docker exec -it elasticsearch /bin/bash

# 在线下载并安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v7.12.1/elasticsearch-analysis-pinyin-7.12.1.zip

#退出
exit
#重启容器
docker restart elasticsearch

要实现根据字母做补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件。地址:https://github.com/medcl/elasticsearch-analysis-pinyin

2. 离线安装

将zip下载到本地, 解压到plugins下的analysis-pinyin文件夹下

3. pinyin分词器的配置参数列表

参数默认值说明注释
keep_first_lettertrue刘德华>ldh是否需要拼音首字母
keep_separate_first_letterfalse刘德华>l,d,h是否需要拼音首字母分开每个字母,为了不让搜索结果过于混乱、无关,这个选项就应该为默认值false
limit_first_letter_length16set max length of the first_letter result(设置 first_letter 结果的最大长度)限制一个长词的各字拼音首字母连起来的最大长度。比如,该值设置成3,有个词是"四大天王",则其拼音首字母为"sdt",取前三个拼音首字母。一般这个字段就为默认。
keep_full_pinyintrue刘德华> [liu,de,hua]
keep_joined_full_pinyinfalse刘德华> [liudehua]这个参数要注意下,是否有必要主动设置成true
keep_none_chinesetruekeep non chinese letter or number in result(结果中保留非中文字母或数字)默认为true,不过一般会主动设置为false,因为在使用pinyin的场景中都是想搜索中文,非中文字符可以过掉
keep_none_chinese_togethertruetrue:DJ音乐家 -> DJ,yin,yue,jia;false:DJ音乐家 -> D,J,yin,yue,jia
keep_none_chinese_in_first_lettertrue刘德华AT2016->ldhat2016在实际使用过程中,该项最好不要默认,主动设置为false,因为本来该插件就是针对中文拼音使用的,大部分查询场景中都是针对中文拼音的搜索,如果将non chinese字符也涵盖进来,容易搜错。
另外,如果某个字段,既可能是中文又可能是英文,同时你默认keep_none_chinese_in_first_letter 为true,则英文会被分成这样:
keep_none_chinese_in_joined_full_pinyinfalseeg: 刘德华2016->liudehua2016是否在joined_full_pinyin中保留非中文字符,看需求决定,如果不需要则需要主动设置为false
none_chinese_pinyin_tokenizetrue如果是拼音,则将非中文字母分成单独的拼音词; eg: liudehuaalibaba13zhuanghan -> liu,de,hua,a,li,ba,ba,13,zhuang,han; 注: keep_none_chinese and keep_none_chinese_together 两项必须先启用默认为true,这个最好主动设置为false。因为非中文字符一般是英文单词或数字,这些如果要被分词的话,会变成这样

分词这么细粒度会导致搜索结果过于模糊。
keep_originalfalse启用此选项时,也将保留原始输入默认为false,看具体需求,再决定是否需要设置为true来保留原始text
lowercasetrue非中文字母转小写
trim_whitespacetrue剔除空格
remove_duplicated_termfalse删除重复的术语; eg: de的 > de
ignore_pinyin_offsettrue在6.0之后,偏移被严格约束,不允许重叠标记,使用该参数,通过忽略偏移将允许重叠标记,请注意,所有与位置相关的查询或突出显示将变得不正确,您应该使用多个字段并为不同的查询目的指定不同的设置。如果需要偏移,请将其设置为false。默认值:true。

IK分词器

1.在线安装ik插件

1
2
3
4
5
6
7
8
9
10
# 进入容器内部
docker exec -it elasticsearch /bin/bash

# 在线下载并安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip

#退出
exit
#重启容器
docker restart elasticsearch

2.离线安装ik插件

将zip下载到本地, 解压到plugins下的ik文件夹下

4)重启容器

1
2
# 4、重启容器
docker restart es
1
2
# 查看es日志
docker logs -f es

5)测试

IK分词器包含两种模式:

  • ik_smart:最少切分

  • ik_max_word:最细切分

1
2
3
4
5
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "学习java太棒了"
}

3 扩展词词典

随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“奥力给”,“传智播客” 等。

所以我们的词汇也需要不断的更新,IK分词器提供了扩展词汇的功能。

1)打开IK分词器config目录:

2)在IKAnalyzer.cfg.xml配置文件内容添加:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
<entry key="ext_dict">ext.dic</entry>
</properties>

3)新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改

1
2
传智播客
奥力给

4)重启elasticsearch

1
2
3
4
docker restart es

# 查看 日志
docker logs -f elasticsearch

日志中已经成功加载ext.dic配置文件

5)测试效果:

1
2
3
4
5
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "传智播客Java就业超过90%,奥力给!"
}

注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑

4 停用词词典

在互联网项目中,在网络间传输的速度很快,所以很多语言是不允许在网络上传递的,如:关于宗教、政治等敏感词语,那么我们在搜索时也应该忽略当前词汇。

IK分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。

1)IKAnalyzer.cfg.xml配置文件内容添加:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典-->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典-->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>

3)在 stopword.dic 添加停用词

1
习大大

4)重启elasticsearch

1
2
3
4
5
6
# 重启服务
docker restart elasticsearch
docker restart kibana

# 查看 日志
docker logs -f elasticsearch

日志中已经成功加载stopword.dic配置文件

注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑

测试分词器安装是否成功

1
2
3
4
GET /_cat/plugins?pretty

VM_113_96_centos analysis-ik 7.9.3
VM_113_96_centos analysis-pinyin 7.9.3

ngram分词器

简介

什么是ngram 分词器?

ngram分词器是ES自带的具有前缀匹配搜索功能的一个文本分词器。它能根据文本的步长逐步对写入的文本内容进行约束切割

ngram和index-time搜索推荐原理

搜索的时候,不用再根据一个前缀,然后扫描整个倒排索引了,而是简单的拿前缀去倒排索引中匹配即可,如果匹配上了,那么就好了,就和match query全文检索一样。

1
2
3
4
5
6
eg:  quick 为列:
Length 1 (unigram): [ q, u, i, c, k ]
Length 2 (bigram): [ qu, ui, ic, ck ]
Length 3 (trigram): [ qui, uic, ick ]
Length 4 (four-gram): [ quic, uick ]
Length 5 (five-gram): [ quick ]

ngram分词实践

1,定义一个索引,并指定分词器ngram;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PUT db_content_testserver2
{
"settings": {
"analysis": {
"analyzer": {
"ngram_analyzer": {
"tokenizer": "ngram_tokenizer"
}
},
"tokenizer": {
"ngram_tokenizer": {
"type": "ngram",
"min_gram": 1,
"max_gram": 5,
"token_chars": [
"letter",
"digit"
]
}
}
}
}
}

2, 创建type,并指定content 字段分词器为"ngram_analyzer"

1
2
3
4
5
6
7
8
9
PUT db_content_testserver2/_mapping/t_article
{
"properties": {
"content": {
"type": "text",
"analyzer": "ngram_analyzer"
}
}
}

3, 写入测试数据

1
2
3
4
POST db_content_testserver2/t_article
{
"content":"据记者了,郑州文化馆、河南省大河文化艺术中心自2016年以来,通过组织第十一届、第十二届中国郑州国际少林武术节书画展,通过书画展文化艺术搭台,是认真贯彻习中央文艺工作座谈会重要讲话精神,响应文化部开展深入生活、扎根人民主题实践活动。以作品的真善美陶冶人类崇高之襟怀品格,树立中华民族的文化自信,用写意精神推动社会文学艺术的繁荣发展。"
}

4,我们可以测试一下上面的分词,是否能正常分词;

1
2
3
4
5
POST db_content_testserver2/_analyze
{
"analyzer": "ngram_analyzer",
"text": "据记者了,郑州文化馆、河南省大河文化艺术中心自2016年以来,通过组织第十一届、第十二届中国郑州国际少林武术节书画展,通过书画展文化艺术搭台,是认真贯彻习中央文艺工作座谈会重要讲话精神,响应文化部开展深入生活、扎根人民主题实践活动。以作品的真善美陶冶人类崇高之襟怀品格,树立中华民族的文化自信,用写意精神推动社会文学艺术的繁荣发展。"
}

wildcard的替代方案: ngram+term/match_phrase

背景

1.ES模糊查询wildcard查询极耗机器CPU资源,查询耗时高,当并发量高时影响ES其它进程。
2.用户实际的模糊查询需求大多是左右模糊匹配。

match_phrase能够实现词组查询

比如brown fox会返回匹配…brown fox…的结果,此结果与wildcard查询传入brown fox的查询结果一致。相当于我们通过match_phrase实现wildcard查询效果,但此时只满足一些特许的模糊查询需求。
那如何对match_phrase的功能进行增强,让其能够满足所有条件?
从上面的查询示例可以看出,brown fox会返回匹配…brown fox…的结果,其根本原因在于索引时ES将…brown fox…分词成了brown,fox等单词。所以只要我们能够控制ES分词效果,将会最终满足我们的需求。而ES提供了丰富的分词功能。

ngram分词能够实现按指定长度对文本进行分词。

ngram可以指定min_gram,max_gram参数实现不同的分词效果。
例如:min_gram,max_gram配置为5时,quick.brown.fox分词后会产生quick,uick.,ick.b,ck.br,k.bro,.brow,brown,rown.,own.f,wn.fo,n.fox。
这时match_phrase会达到什么效果了?
a.用户输入quick,brown,k.bro等都能够返回quick.brown.fox。
b.用户输入brown.fox,brown.fox会被分词成brown,rown.,own.f等,此时同样会返回quick.brown.fox。
c.用户输入fox(长度小于ngram分词配置的分词长度时),不会返回任何结果。

如何正确返回查询条件长度小于5时的结果

利用ngram分词 + term查询可以实现所需查询效果。
ngram分词配置:min_gram配置为1,max_gram配置为4。例如quick将会被分词为q,u,i,… quic,uick。
term查询会对用户输入的条件进行精确匹配,比如输入uic,会返回quick。

方案

查询条件长度小于5时:使用ngram分词 + term查询
查询条件长度大于等于5时:使用ngram分词 + match_phrase查询

实施

创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
PUT index_text_1
{
"settings": {
# 表示最大允许的分词间隔:`fix: The difference between max_gram and min_gram in NGram Tokenizer must be less than or equal to: [1] but was [3]`
"index.max_ngram_diff":5,
"analysis": {
"analyzer": {
"ngram_analyzer_short": {
"filter": "lowercase",
"tokenizer": "ngram_tokenizer_short"
},
"ngram_analyzer_long": {
"filter": "lowercase",
"tokenizer": "ngram_tokenizer_long"
}
},
"tokenizer": {
"ngram_tokenizer_short": {
"type": "ngram",
"min_gram": "1",
"max_gram": "4"
},
"ngram_tokenizer_long": {
"type": "ngram",
"min_gram": "5",
"max_gram": "5"
}
}
}
},
"mappings": {
"properties": {
"char": {
"type": "keyword",
"fields": {
"long_char": {
"type": "text",
"analyzer": "ngram_analyzer_long"
},
"short_char": {
"type": "text",
"analyzer": "ngram_analyzer_short"
}
}
}
}
}
}

新增数据

1
2
3
4
5
6
7
POST /index_text_1/_doc/_bulk
{"index":{"_id":1}}
{"char":"nHRSPkkXLGm6UsmRbRBFQYRCRXpp6CXrnBiqSR"}
{"index":{"_id":2}}
{"char":"quick.brown.fox"}
{"index":{"_id":3}}
{"char":"elasticsearch"}

查询语句

查询条件长度大于等于5时

1
2
3
4
5
6
7
8
9
10
GET index_text_1/_search
{
"query": {
"match_phrase": {
"char.long_char": {
"query": "UsmRbRBFQY"
}
}
}
}

查询条件长度小于5时

1
2
3
4
5
6
7
8
9
10
GET index_text_1/_search
{
"query": {
"term": {
"char.short_char": {
"value": "fox"
}
}
}
}

ngram使用思考

查询条件长度该设置成多少?
值太大,占用的存储资源就多(主要是short_char字段);值太小,long_char字段分词被分的太细,match_phrase查询耗时就会增加。

edge_gram分词器

这是一种倒金字塔的格式,预先把文本按这种方式给索引起来,然后按term查询即可(如果按IK分词,可能就分不出 “中华人民共” 这样一个词来)。

1
2
3
4
5
6
7
中华人民共和国
中华人民共和
中华人民共
中华人民
中华人
中华

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
PUT xiaowu_test
{
"settings":{
"analysis":{
"filter":{
"edge_ngram_filter":{
"type":"edge_ngram",
"min_gram":1,
"max_gram":50
},
"analyzer":{
"ngramIndexAnalyzer":{
"type":"custom",
"tokenizer":"keyword",
"filter":[
"edge_ngram_filter",
"lowercase"
]
},
"ngramSearchAnalyzer":{
"type":"custom",
"tokenizer":"keyword",
"filter":[
"lowercase"
]
}
}
}
}
}
}

其他分词器

[1] 如何在elasticsearch中使用分词器: https://www.elastic.co/guide/en/elasticsearch/reference/current/configure-text-analysis.html
[2] 常用的中文分词器: https://github.com/medcl/elasticsearch-analysis-ik
[3] 拼音分词器: https://github.com/medcl/elasticsearch-analysis-pinyin
[4] elasticsearch官方文档中的一段介绍: https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html#analyzer-anatomy
[5] 分析器: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html
[6] 自定义: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html
[7] 字符过滤器: https://github.com/elastic/elasticsearch/edit/7.13/docs/reference/analysis/anatomy.asciidoc
[8] 字符过滤器: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html
[9] 分词器: https://github.com/elastic/elasticsearch/edit/7.13/docs/reference/analysis/anatomy.asciidoc
[10] whitespace: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-whitespace-tokenizer.html
[11] 分词器: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html
[12] token过滤器: https://github.com/elastic/elasticsearch/edit/7.13/docs/reference/analysis/anatomy.asciidoc
[13] lowercase: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html
[14] stop: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-tokenfilter.html
[15] synonym: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html
[16] 词干提取token filters: https://www.elastic.co/guide/en/elasticsearch/reference/current/stemming.html
[17] token graphs: https://www.elastic.co/guide/en/elasticsearch/reference/current/token-graphs.html
[18] token过滤器: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html
[19] 内置分析器示例: https://www.elastic.co/guide/en/elasticsearch/reference/current/configuring-analyzers.html
[20] standard分析器: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html
[21] character filters: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html
[22] tokenizer: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html
[23] token filters: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html
[24] 配置: https://github.com/elastic/elasticsearch/edit/7.13/docs/reference/analysis/analyzers/custom-analyzer.asciidoc
[25] 内置分析器类型: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html
[26] tokenizer: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html
[27] character filters: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html
[28] token filters: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html
[29] position_increment_gap: https://www.elastic.co/guide/en/elasticsearch/reference/current/position-increment-gap.html
[30] 示例配置: https://github.com/elastic/elasticsearch/edit/7.13/docs/reference/analysis/analyzers/custom-analyzer.asciidoc
[31] HTML 标记字符过滤器: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-htmlstrip-charfilter.html
[32] 标准分词器: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-tokenizer.html
[33] Lowercase Token Filter: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html
[34] ASCII-Folding Token Filter: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-asciifolding-tokenfilter.html
[35] Mapping Character Filter: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-mapping-charfilter.html
[36] Pattern Tokenizer: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-pattern-tokenizer.html
[37] Lowercase Token Filter: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html
[38] Stop Token Filter: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-tokenfilter.html

[39] ngram Tokenizer: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html