ES 中默认的分词器是 Standard Analyzer,会对文本内容按单词分类并进行小写处理,但是主要是用于处理英文的,对中文的分词处理就非常不友好了。理解了分词,自己去做搜索的时候就会少一些为什么搜索的结果和预期不符的疑惑。
1、为什么需要中文分词器插件
先使用 Kibana 测试 ES 默认分词器对英文的处理:
GET _analyze
{
"text": "Hello World"
}
结果如下:
所以 ES 默认分词器对英文的处理是符合预期的,同时大写字母也被转为小写。
但是对中文的处理呢?
GET _analyze
{
"text": "你好世界"
}
很显然无法对中文正确的分词。那这样又会导致什么问题呢?
我们先创建一个test
索引,并添加几条文档数据:
PUT test
POST test/_doc
{
"content": "Hello World"
}
POST test/_doc
{
"content": "你好世界"
}
POST test/_doc
{
"content": "好好学习"
}
操作完成后,可以在 head 工具中看到如下数据:
我们先通过hello
来查询数据:
GET test/_search
{
"query": {
"match": {
"content": "hello"
}
}
}
查询结果如下,可以得到预期的数据:
再查询你好
试试:
GET test/_search
{
"query": {
"match": {
"content": "你好"
}
}
}
结果如下,我们期望只查出你好世界
,但好好学习
也被查出来了,原因是就查询时你好
被分词成了你
、好
两个字,而不是一个完整的词。由于我们没有设置分词器,使用的是默认的分词器,所以在保存文档数据时content
字段的中文内容也会被分词成单个字,并生成索引,查询时会使用被分词后的关键字去content
生成的索引中匹配,自然会匹配出多条文档数据:
所以问题很明显了,由于使用了 ES 默认的分词器,导致查询中文时不能按照我们的预期得到想要的结果,所以我们一般都会单独安装中文分词器插件,ES 中使用比较多的中文分词器插件是elasticsearch-analysis-ik,简称 IK 分词器。
2、安装 IK 分词器
它的原码托管在 GitHub 上,主页地址 https://github.com/medcl/elasticsearch-analysis-ik。需要注意的是,不同版本的 IK 分词器对应的 ES 版本是不同的 ,我们的 ES 使用的是7.9.3版本,下载对应版本的 IK 分词器即可。安装比较简单可以参考如下步骤:
- 下载和 ES 版本对应的分词器压缩包 https://github.com/medcl/elasticsearch-analysis-ik/releases
- 在每个 ES 节点安装目录的 plugins 文件夹下创建名为 ik 的文件夹,并将下载好的分词器压缩包解压到里边
- 重启 ES 服务
IK 分词器有如下两种分词模式:
-
ik_max_word
,会对文本做最细粒度的拆分,尽可能拆分出多的词。一个字段的值需要被全文检索式,可以在创建索引时设置字段的分词模式指定为ik_max_word
,这样字段内容会被最大化的分词进而生成对应的索引,这样对应的文档能更准确的被检索到。 -
ik_smart
,会对文本做最粗粒度的拆分,拆分出的词相对会少些。一般检索时可以设置关键字的分词模式为ik_smart
,这样能更准确的检索到预期的结果。
3、测试
首先我们测试使用 IK 分词器后的分词效果:
GET _analyze
{
"analyzer": "ik_smart",
"text": "你好世界"
}
可以看到 IK 分词器已经可以按照我们的预期分词了:
在测试查询之前,我们先删掉之前的test
索引,重新创建索引,并指定字段索引时的分词模式(analyzer
),以及检索式关键字的分词模式(search_analyzer
):
PUT test
{
"mappings": {
"properties": {
"content":{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
}
添加的数据和之前一样,我们再尝试查询你好
:
GET test/_search
{
"query": {
"match": {
"content": "你好"
}
}
}
结果符合预期:
3、自定义扩展字典
一些新的词汇或者特殊词汇,IK 分词器可能并不会识别,还是会给我们拆分成单个字,例如这两个词唐家三少
、南派三叔
,先测试下 IK 分词器对唐家三少
的分词效果:
GET _analyze
{
"analyzer": "ik_smart",
"text": "唐家三少"
}
很显然 IK 分词器并不会把它当做一个完整词去处理(南派三叔
的情况也是类似的):
我们再给索引添加如下两条数据:
POST test/_doc
{
"content": "唐家三少"
}
POST test/_doc
{
"content": "南派三叔"
}
试着查询一下唐家三少
:
GET test/_search
{
"query": {
"match": {
"content": "唐家三少"
}
}
}
我们希望的是查询唐家三少
时,只查询出唐家三少
所在的文档数据,让 IK 分词器把它当做一个词去处理,而不是直接拆分成单个字。我们可以自定义扩展字典来解决这个问题。
3.1、本地字典
定义本地扩展字典的步骤如下:
-
在分词器插件目录的 config 目录下创建 my.dic 文件(文件名不做限制,但后缀名必须是
.dic
),然后添加要扩展的词汇:
-
定义好了扩展字典,接下来需要在分词器插件目录的 config 目录下的configIKAnalyzer.cfg.xml 文件中配置定义的字典:
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">my.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
- 重启 ES 服务
3.2、测试
完成了上述配置,我们再尝试查询唐家三少
:
GET test/_search
{
"query": {
"match": {
"content": "唐家三少"
}
}
}
实现了我们预期的效果:
3.3、远程字典
如果每次修扩展字典都要重启 ES 服务也是挺麻烦的,我们可以通过定义远程扩展字典来解决这个问题。
我们要做的就是提供一个接口,返回扩展字典文件即可。我们这里创建一个 Spring Boot 项目,将自定义的词典放在静态资源目录即可:
这样我们就可以通过接口 http://localhost:8080/my.dic 访问到远程字典,然后在 configIKAnalyzer.cfg.xml 中配置远程扩展字典即可:
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<!--<entry key="ext_dict">my.dic</entry> -->
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://localhost:8080/my.dic</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
网友评论