Elasticsearch中文分词器怎么用
Elasticsearch中文分词器的介绍、选型对比和用法。
一、背景
1. 必须要理解清楚的术语
lucene的分析器(analyzer)以及它的三个组成成分:
字符过滤器(character filters)、分词器(tokenizers)和过滤器(token filters)。
下面分别解释下这四个专业术语:
- analyzer:由字符过滤器、分词器跟过滤器组成,他的功能就是:将分词器跟分析器进行合理的组合,使之产生对文本分词和过滤效果。因此,分析器使用分词和过滤器构成一个管道,文本在「滤过」这个管道之后,就成为可以进入索引的最小单位。
- character filters:预处理,共有三种。
- mapping char filter 通过给定的mappings(mappings数组或者读取外部文件)进行数据的替换;
- html strip char filter 把数据中的html标签元素剥离出来,例如
<a>
变成a
; - pattern replace char filter 用正则表达式的方式来替换数据。
- tokenizers:主要用于对文本资源进行切分,将文本规则切分为一个个可以进入索引的最小单元
- token filters:主要对分词器切分的最小单位进入索引进行预处理,如:大写转小写,复数转单数,也可以复杂(根据语义改写拼写错误的单词)
2. analyzer的内部机制
前面有篇文章整理过了,点击查看。
3. 各种分词器的对比与使用方法
网上有篇文章整理的很好,这里就不重复整理了,点击查看。
二、安装
1. 环境
- Ubuntu 14.04/16、04
- JDK1.8
- Elasticsearch 5.3
- Kibana 5.3.2
2. 步骤
我这里使用 ik-analyzer + pinyin 分词器。
ik-analyzer:https://github.com/medcl/elasticsearch-analysis-ik
pinyin分词器:https://github.com/medcl/elasticsearch-analysis-pinyin
繁简切换分词器:https://github.com/medcl/elasticsearch-analysis-stconvert
安装方式都是选择相应版本的插件后,下载到es安装目录的 plugins/
下,解压缩。
解压缩前最好在 plugins/
目录下新建两个目录,分别为 ik
和 pinyin
,因为解压完的文件比较多,这样便于区分。
最后,重启 es。
三、简单测试
1. 测试ik分词器
使用 ik_smart
分词器,会做最粗粒度的拆分;已被分出的词语将不会再次被其它词语占有。
GET _analyze
{
"analyzer":"ik_smart",
"text":"艾泽拉斯国家地理"
}
使用 ik_max_word
分词器,会将文本做最细粒度的拆分;尽可能多的拆分出词语。
GET _analyze
{
"analyzer":"ik_max_word",
"text":"艾泽拉斯国家地理"
}
2. 测试拼音分词器
就是普通的把汉字转换成拼音,提取汉字的拼音首字母。
GET _analyze
{
"analyzer":"pinyin",
"text":"艾泽拉斯国家地理"
}
3. 测试简/繁体分词器
默认是简体转换成繁体
GET _analyze
{
"analyzer":"stconvert",
"text":"艾泽拉斯国家地理"
}
四、实战
我在实际业务中,用过下列几种方案。
1. 方案1:自定义分析器
需求
① 设定一个分词器,集成中文、英文、拼音
② 原理:先进行中文分词、再进行拼音分词
③ 弊端:同音词问题
索引设计
创建一个索引,并设置index分析器相关属性:
分析器名称为 nb_analyzer
PUT /single-analyzer-demo
{
"settings": {
"refresh_interval": "5s",
"number_of_shards": 1,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"nb_analyzer": {
"type": "custom",
"tokenizer": "ik_smart",
"filter": [
"lowercase",
"stemmer",
"pinyin_filter"
],
"char_filter": [
"html_strip"
]
}
},
"filter": {
"pinyin_filter": {
"type": "pinyin",
"keep_first_letter": false,
"keep_separate_first_letter": false,
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_none_chinese": true,
"keep_none_chinese_together": true,
"keep_none_chinese_in_first_letter": true,
"keep_none_chinese_in_joined_full_pinyin": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"demo_type": {
"properties": {
"name": {
"type": "text",
"analyzer": "nb_analyzer"
},
"introduction": {
"type": "text",
"analyzer": "nb_analyzer"
},
"details": {
"type": "text",
"analyzer": "nb_analyzer"
}
}
}
}
}
测试分词效果
这一步比较重要,可以先看看分词效果是不是自己想要的。
GET /single-analyzer-demo/_analyze
{
"text": ["安全气囊产气药you are a dogs"],
"analyzer": "nb_analyzer"
}
插入两条数据
插入两条,稍后查询时候通过加权来控制评分
POST single-analyzer-demo/demo_type
{
"name": "dog",
"introduction": "hello",
"details": "name is dog"
}
POST single-analyzer-demo/demo_type
{
"name": "hello",
"introduction": "dog",
"details": "introdiction is dog"
}
查询语法
通过改变权重,查看查询到的评分区别。
GET single-analyzer-demo/_search
{
"highlight": {
"fields": {
"name": {
"fragment_size": 50
},
"introduction": {
"fragment_size": 50
},
"details": {
"fragment_size": 150
}
},
"post_tags": [
"</em>"
],
"order": "score",
"pre_tags": [
"<em>"
]
},
"query": {
"query_string": {
"query": "dog",
"fields": [
"name^2",
"introduction^3",
"details"
],
"minimum_should_match": "75%"
}
},
"_source": [
"name",
"introduction",
"details"
]
}
2. 方案2:切分到不同的域
这种方案适用于一份文本有各种国家的翻译,然后把每份翻译存储在不同的域中,根据域的语言决定使用相应的分析器。
这里不多介绍,需要了解的话见官网。
3. 方案3:使用multi_field为搜索字段建立不同类型的索引
参考
参考了一下这篇文章。
需求
① 中文搜索、英文搜索、中英混搜,如:「南京东路」,「cafe 南京东路店」。
② 全拼搜索、首字母搜索、中文+全拼、中文+首字母混搜,如:「nanjingdonglu」,「njdl」,「南京donglu」,「南京dl」,「nang南东路」,「njd路」等等组合。
③ 简繁搜索、特殊符号过滤搜索,如:「龍馬」可通过「龙马」搜索,再比如 L.G.F 可以通过 lgf 搜索,café 可能通过 cafe 搜索。
④ 排序优先级为:以关键字开头>包含关键字(没做)。
索引设计
通过在主域下使用多个子域,每个子域使用各自的分析器。(待处理的语言有限的情况用)
使用 most_fields query type(多字段搜索语法) 来让我们可以用多个字段来匹配同一段文本。
使用 multi_field 为搜索字段建立不同类型的索引,有全拼索引、首字母简写索引、Ngram 索引以及 IK 索引,从各个角度分别击破,然后通过 char-filter 进行特殊符号与简繁转换。
注意:
char_filter 部分:通过给定的 mappings 数据来替换。直接给 mappings 数据或者将 mappings 数据写到配置文件,给出配置文件的路径。默认在config/mappings.txt。测试时,删掉下面 mappings_path 那一行。
PUT /multi-analyzer-demo
{
"settings": {
"refresh_interval": "5s",
"number_of_shards": 1,
"number_of_replicas": 1,
"analysis": {
"char_filter": {
"special_char_convert": {
"type": "mapping",
"mappings_path": "char_filter_mappings.txt",
"mappings": [
"à=>a",
"á=>a"
]
},
"t2s_char_convert": {
"type": "stconvert",
"convert_type": "t2s"
}
},
"filter": {
"pinyin_filter": {
"type": "pinyin",
"keep_first_letter": false,
"keep_separate_first_letter": false,
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_none_chinese": true,
"keep_none_chinese_together": true,
"keep_none_chinese_in_first_letter": true,
"keep_none_chinese_in_joined_full_pinyin": true,
"none_chinese_pinyin_tokenize": false,
"lowercase": true
}
},
"analyzer": {
"elastic_ik_analyzer": {
"type": "custom",
"char_filter": [
"html_strip",
"t2s_char_convert",
"special_char_convert"
],
"tokenizer": "ik_smart",
"filter": [
"lowercase",
"stemmer"
]
},
"elastic_pinyin_analyzer": {
"type": "custom",
"char_filter": [
"html_strip",
"t2s_char_convert",
"special_char_convert"
],
"tokenizer": "ik_smart",
"filter": [
"lowercase",
"pinyin_filter"
]
}
}
}
},
"mappings": {
"demo_type": {
"properties": {
"name": {
"type": "text",
"fields": {
"ik": {
"type": "text",
"analyzer": "elastic_ik_analyzer"
},
"pinyin": {
"type": "text",
"analyzer": "elastic_pinyin_analyzer"
}
}
},
"introduction": {
"type": "text",
"fields": {
"ik": {
"type": "text",
"analyzer": "elastic_ik_analyzer"
},
"pinyin": {
"type": "text",
"analyzer": "elastic_pinyin_analyzer"
}
}
},
"details": {
"type": "text",
"fields": {
"ik": {
"type": "text",
"analyzer": "elastic_ik_analyzer"
},
"pinyin": {
"type": "text",
"analyzer": "elastic_pinyin_analyzer"
}
}
}
}
}
}
}
检查一下分词效果
中文、繁体和英文
GET /multi-analyzer-demo/_analyze
{
"text": ["我爱祖国"],
"analyzer": "elastic_ik_analyzer"
}
拼音
GET /multi-analyzer-demo/_analyze
{
"text": ["我爱祖国"],
"analyzer": "elastic_pinyin_analyzer"
}
插入数据
插入三条,稍后查询时候通过加权来控制评分,注意同音词问题。
POST multi-analyzer-demo/demo_type
{
"name": "张三",
"introduction": "一个前端工程师",
"details": "名字是张三"
}
POST multi-analyzer-demo/demo_type
{
"name": "章三",
"introduction": "一个后端工程师",
"details": "名字是章三"
}
POST multi-analyzer-demo/demo_type
{
"name": "神秘人",
"introduction": "一个叫张三的全栈工程师",
"details": "名字在introduction里面"
}
查询语法
可以给个别字段加权(查询时每个字段默认的权重是 1)
设置最少应当匹配数来减少低质量的匹配。
GET multi-analyzer-demo/_search
{
"highlight": {
"fields": {
"name.ik": {
"fragment_size": 50
},
"name.pinyin": {
"fragment_size": 50
},
"introduction.ik": {
"fragment_size": 50
},
"introduction.pinyin": {
"fragment_size": 50
},
"details.ik": {
"fragment_size": 150
},
"details.pinyin": {
"fragment_size": 150
}
},
"post_tags": [
"</em>"
],
"order": "score",
"pre_tags": [
"<em>"
]
},
"query": {
"query_string": {
"query": "zhang",
"fields": [
"name.ik",
"name.pinyin",
"introduction.ik",
"introduction.pinyin",
"details.ik",
"details.pinyin"
],
"minimum_should_match": "75%"
}
},
"_source": [
"name",
"introduction",
"details"
],
"from": 0,
"size": 20
}
考虑优化
query fields 里面是否要进一步加权控制,可以自行尝试下。
五、补充:ik添加自定义词库
1. 创建自己的词库
首先在 ik 插件的 config/custom
目录下创建一个文件 my.dic
(名字任意,以.dic结尾)。
在文件中添加词语即可,每一个词语一行。
注意: 这个文件可以在 linux 中直接 vim 生成, 或者在 windows 中创建之后上传到这里。
如果是在 linux 中直接 vim 生成的,可以直接使用。
如果是在 windows 中创建的,需要注意文件的编码必须是 UTF-8 without BOM 格式。
2. 修改ik的配置文件
默认情况下 ik 的配置文件就在 ik 插件的 config
目录下面,名字为 IKAnalyzer.cfg.xml
。
把刚才创建的文件所在位置添加到 ik 的配置文件中即可。vim config/IKAnalyzer.cfg.xml
即需要把 my.dic
文件的位置添加到 key=ext_dict
这个 entry
中。
注意:下面第 6 行的 ;custom/my.dic 是我新增的:
<?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">custom/mydict.dic;custom/single_word_low_freq.dic;custom/my.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
六、补充:ik配置远程扩展词库
1. 好处
可以使用其他程序调用更新,且不用重启 ES,很方便。
2. 配置
即需要把 请求地址 添加到 key=remote_ext_dict
这个 entry 中。
注意:下面第 10 行的 http://192.168.1.136/hotWords.php 是我新增的:
<?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">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">http://192.168.1.136/hotWords.php</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
3. 远程扩展字典
远程词典,那么就要是一个可访问的链接,可以是一个页面,也可以是一个txt的文档,但要保证输出的内容是 utf-8 的格式。
ik 接收两个返回的头部属性 Last-Modified 和 ETag,只要其中一个有变化,就会触发更新,ik 会每分钟获取一次。
hotWords.php 的内容:
$s = <<<'EOF'
陈港生
元楼
蓝瘦
EOF;
header('Last-Modified: '.gmdate('D, d M Y H:i:s', time()).' GMT', true, 200);
header('ETag: "5816f349-19"');
echo $s;
七、总结
以上就是我使用ES中文分词器的笔记,如有问题或建议,可以留言。