五 文档分析
在Elasticsearch中,通过分析器来对文档的文本内容进行分析,将其中的文本内容进行分词化和规范化,这个过程被称作文档分析。
5.1 什么是分析器
在Elasticsearch中,分析器是一个软件模块,它主要负责两个功能:Tokenization(分词化)和Normalization(规范化)。
-
Tokenization
Tokenization,也即分词化是将句子拆分成单个单词的过程,它遵循一定的规则。
-
Normalization
Normalization,也即规范化是以词干提取、同义词、停用词和其他特征的形式对分词(词)进行处理、转换、修改和丰富的过程。
分析器由一个分词器(tokenizers)、零个或多个字符过滤器(character filters)、零个或多个**词项过滤器(token filters)**组成,其结构图如下图所示。
-
字符过滤器
对输入的文本内容进行处理,应用于字符级别。
-
分词器
对于被字符过滤器处理后的字符流进行进一步处理,将文本按照规则划分成若干词项。
-
词项过滤器
对于被分词器切分的词项进行进一步处理。
字符过滤器、分词器、词项过滤器会在后续章节进行详细介绍。
- 分析器工作的整体流程
- 文本内容先要经过字符过滤器的过滤和处理
- 分词器对第一步处理的文本内容按照指定规则进行处理,将其拆分成若干词项。
- 对第二步拆分出来的词项做进一步处理
- 将第三步处理好的词项添加到倒排索引中。
5.2 字符过滤器
5.2.1 作用
字符过滤器应用于字符级别,文本的每个字符都会按照顺序通过这些过滤器,这些过滤器可以执行以下特定功能:
-
从输入流中删除不需要的字符
例如,可以从输入文本中清除
、、 等 HTML 标记。
-
添加或替换现有流中的其他字符
例如,希腊字母与等效的英语单词(0和1替换成false和true)。
Elasticsearch 提供了三个字符过滤器,我们可以使用它们构建自己的定制分析器。
5.2.2 html_strip 字符过滤器
-
作用
html_strip 字符过滤器去除像 这样的 HTML 元素并解码像 & 这样的 HTML 实体。
-
示例
使用html_strip 字符过滤器处理下列文本
<h1>html_strip & test hellow</h1>
文本经过该字符过滤器后,只剩下文本"html_strip & test hellow"。我们可以发送get/post请求到http://localhost:9200/_analyze,并且在请求体中加入下列参数
{ "text":"<h1>html_strip & test hellow</h1>", // 要解析的文本内容 "tokenizer": "standard", // 使用的分词器是标准分词器,后面小节会进行讲解 "char_filter": ["html_strip"] // 使用的字符过滤器 }
可以得到结果如下:
// 返回值 { "tokens": [ { "token": "html_strip", "start_offset": 4, "end_offset": 14, "type": "<ALPHANUM>", "position": 0 }, { "token": "test", "start_offset": 17, "end_offset": 21, "type": "<ALPHANUM>", "position": 1 }, { "token": "hellow", "start_offset": 22, "end_offset": 28, "type": "<ALPHANUM>", "position": 2 } ] }
上面的返回值可以看到,经过字符过滤器html_strip和标准分词器的处理后,Elasticsearch将输入的文本内容拆分成了三个词,HTML元素已经从里面被去除了。
标准分词器会按照单个单词来拆分文本内容,并忽略一部分特殊字符如&,后面会进行讲解。
-
保留部分HTML元素
使用html_strip字符过滤器会将文本内容中的所有HTML元素去除掉,与此同时,html_strip字符过滤器支持保留部分HTML元素。我们可以使用html_strip字符过滤器的扩展属性escaped_tags来指定需要保留的HTML元素。
例如,我们输入下列文本
<pre><h1>html_strip & test hellow</h1></pre>
我们想要文本内容在通过html_strip字符过滤器后,剩余下列文本
<pre>html_strip & test hellow</pre>
这时,我们就无法再简单的使用html_strip字符过滤器了,我们需要自定义一个分析器。(后续会详细讲解各个参数,这里我们主要关注html_strip字符过滤器的扩展属性escaped_tags即可)
创建使用自定义分析器的索引
// PUT http://localhost:9200/character-filter-test // body内容 { "settings": { "analysis": { "analyzer": { // 当前索引中的分析器集合 "test_html_strip_filter_analyzer": { // 自定义的分析器名称 "tokenizer": "keyword", "char_filter": ["my_html_strip_filter"] // 自定义的分析器使用的字符过滤器集合 } }, "char_filter": { // 当前索引中的字符过滤器集合 "my_html_strip_filter": { // 自定义字符过滤器的名称 "type": "html_strip", // 字符过滤器类型为html_strip "escaped_tags": ["pre"] // 要保留的html元素名 } } } } }
使用自定义分析器分析文本
// POST http://localhost:9200/character-filter-test/_analyze // body内容 { "text":"<pre><h1>html_strip & test hellow</h1></pre>", // 要分析的文本内容 "analyzer": "test_html_strip_filter_analyzer" // 使用的分析器名称 }
结果如下:
由结果可以看到,分词后的结果中没有h1标签,但是有pre标签。
注意:这里的结果包含了&,这是因为我们的分析器中的分词器使用的是keyword,后面会详细介绍,这里重点关注保留了pre标签即可。
5.2.3 mapping 字符过滤器
-
作用
Mapping 字符过滤器用指定的替换字符串替换任何出现的指定字符串。
-
示例
将输入的文本内容中的“CN”替换成"中国"。
// POST http://localhost:9200/_analyze // body内容 { "text":"I come from CN", "tokenizer": "keyword", "char_filter": [{ "type":"mapping", "mappings":[ "CN => 中国" ] }] }
结果如下:
可以看到内容中的CN已经被替换成了中国
-
使用自定义分析器
在正式使用时,一般会结合自定义分析器来使用。
创建带有自定义mapping字符过滤器的索引
// PUT http://localhost:9200/mapping-filter-test // body内容 { "settings": { "analysis": { "analyzer": { "test_mapping_filter_analyzer": { "tokenizer": "keyword", "char_filter": ["my_mapping_filter"] } }, "char_filter": { "my_mapping_filter": { "type": "mapping", "mappings": [ "CN => 中国", "HUBEI => 湖北", "WUHAN => 武汉" ] } } } } }
测试分词
// GET http://localhost:9200/mapping-filter-test/_analyze // body内容 { "text":"I come from WUHAN,HUBEI,CN", "analyzer": "test_mapping_filter_analyzer" }
结果如下:
可以看到,输入的文本内容已经按照我们配置的映射进行了文本替换。
-
使用配置文件
当我们的mapping映射很多时,再使用这种脚本方式就不便于维护了。因此Elasticsearch支持通过配置mapping字符过滤器的属性mappings_path来读取指定目录下的文件,从而获取mapping映射。
在Elasticsearch安装目录下的config子目录中,创建analysis文件夹,在其中放置my_mapping.txt文件,并在文件中维护对应的映射,如下图所示。
注意:该txt文件必须是UTF-8编码,并且每个映射之间需要用换行符分隔。
创建索引进行测试
// PUT http://localhost:9200/mapping-filter-test2 // body内容 { "settings": { "analysis": { "analyzer": { "test_mapping_filter_analyzer": { "tokenizer": "keyword", "char_filter": ["my_mapping_filter"] } }, "char_filter": { "my_mapping_filter": { "type": "mapping", "mappings_path": "analysis/my_mapping.txt" } } } } }
mappings_path需要指定读取的配置文件,该路径可以是基于config目录的相对路径或者绝对路径。
// GET http://localhost:9200/mapping-filter-test2/_analyze // body内容 { "text":"I come from WUHAN,HUBEI,CN", "analyzer": "test_mapping_filter_analyzer" }
结果如下:
5.2.4 pattern_replace 字符过滤器
-
作用
pattern_replace 字符过滤器用指定的替换替换与正则表达式匹配的任何字符。
-
示例
这里,我们直接使用自定义分析器来进行演示,替换文本内容中的电话号码为中文“这是一段电话号码”。
// PUT http://localhost:9200/pattern-replace-filter-test // body内容 { "settings": { "analysis": { "analyzer": { "test_pattern_replace_filter_analyzer": { "tokenizer": "keyword", "char_filter": ["my_pattern_replace_filter"] } }, "char_filter": { "my_pattern_replace_filter": { "type": "pattern_replace", "pattern": "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$", "replacement": "这是一段电话号码" } } } } }
测试
// GET http://localhost:9200/pattern-replace-filter-test/_analyze // body内容 { "text":"13111111111", "analyzer": "test_pattern_replace_filter_analyzer" }
结果如下:
5.3 分词器
5.3.1 作用
分词器是对文本进行分析处理的一种手段,基本处理逻辑为按照预先制定的分词规则,把原始文本分割成若干更小粒度的词项,粒度大小取决于分词器规则。
其工作时机在字符过滤器(若存在)之后。
系统内置了一些分词器,我们会详细介绍几种常用的分词器,其他分词器详见官方文档
5.3.2 standard 分词器
-
作用
该分词器会按照Unicode文本分割算法定义的词边界将文本划分为若干词项,并且分词器会去掉大部分的标点符号。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I like steak !", "tokenizer": "standard" }
结果如下:
可以看到,每个词分为了一个词项,且去掉了感叹号。
5.3.3 letter 分词器
-
作用
该分词器会在非字母文本处进行分割,即,当相邻词之间是非字母文本时,就会进行分割,并且也会去掉非字母的文本。
注意:遇到中文时会按照是字母来处理,即,遇到中文时并不分割。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I,like&steak和西红柿!and#you", "tokenizer": "letter" }
结果如下:
5.3.4 whitespace 分词器
-
作用
该分词器在遇到空格时进行分割。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I like&steak!", "tokenizer": "whitespace" }
结果如下:
5.3.5 lowercase 分词器
-
作用
该分词器和letter分词器类似,会在非字母文本处进行分割,并且它会将分割出来的每个词项转成小写,此过滤器也会丢弃非字母文本的词项。
相当于是letter分词器和lowercase词项过滤器的结合,但是性能更高。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I,like&steak和西红柿!and#you", "tokenizer": "lowercase" }
结果如下:
5.3.6 classic 分词器
-
作用
该分词器基于语法规则对文本进行分词,对英语文档分词非常有用,在处理首字母缩写,公司名称,邮件地址和Internet主机名上效果非常好。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"My mail is 123456@qq.com", "tokenizer": "classic" }
结果如下:
5.3.7 keyword 分词器
-
作用
该分词器是一个“noop”标记器,它接收给定的任何文本,并输出作为单个词项的完全相同的文本。它可以结合词项过滤器来规范化输出,例如小写的电子邮件地址。
即,将输入的文本当做一个整体
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I like steak !", "tokenizer": "keyword" }
结果如下:
5.3.8 pattern 分词器
-
作用
该使用正则表达式在匹配单词分隔符时将文本拆分为词项,或者将匹配的文本捕获为词项。
默认模式是\W+,它在遇到非单词字符时拆分文本。
-
示例
匹配单词分隔符时将文本拆分成词项
// GET http://localhost:9200/_analyze // body内容 { "text":"test,pattern,tokenizer!!!", "tokenizer": { "type":"pattern", "pattern":"," } }
按照“,”分词
结果如下:
将匹配的文本捕获为词项
// GET http://localhost:9200/_analyze // body内容 { "text":"\"test\"\"pattern,tokenizer!!!\"", "tokenizer": { "type":"pattern", "pattern":"\"((?:\\\\\"|[^\"]|\\\\\")+)\"", "group":1 } }
捕获被双引号括起来的值
这里的group,参考Java的Matcher对象的group() api
结果如下:
5.4 词项过滤器
5.4.1 作用
词项过滤器接受来自分词器的词项流,并可以修改词项(例如小写),删除词项(例如删除停止词)或添加词项(例如同义词)。
系统内置了一些词项过滤器器,我们会详细介绍几种常用的词项过滤器,其他词项过滤器详见官方文档
5.4.2 lowercase 词项过滤器
-
作用
该词项过滤器会将每个词项由大写改为小写,例如Man改为man,并且它支持希腊语、爱尔兰语和土耳其语的小写转换。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I Like StEak !", "tokenizer": "keyword", "filter" : ["lowercase"] }
结果如下:
5.4.3 uppercase 词项过滤器
-
作用
该词项过滤器会将每个词项由小写改为大写,例如man改为MAN。
注意:
根据语言的不同,一个大写字符可以映射到多个小写字符。使用大写过滤器可能会导致丢失小写字符信息,因此更推荐使用lowercase词项过滤器
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I Like StEak !", "tokenizer": "keyword", "filter" : ["uppercase"] }
结果如下:
5.4.4 length 词项过滤器
-
作用
删除短于或长于指定字符长度的词项。例如,可以排除短于2个字符的词项和长于5个字符的词项。
注意:
该过滤器会删除不满足条件的整个词项。如果希望将词项缩短到特定长度,请使用 truncate 词项过滤器。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text": "I Like StEak !", "tokenizer": "standard", "filter": [{ "type": "length", "min": 2, "max": 4 }] }
结果如下:
5.4.5 truncate 词项过滤器
-
作用
该过滤器会截断超出指定字符限制的词项。此限制默认为10,但可以使用length参数自定义。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text": "I Like StEak !", "tokenizer": "keyword", "filter": [{ "type": "truncate", "length": 5 }] }
结果如下:
5.4.6 ASCII folding 词项过滤器
-
作用
该过滤器会将不在Basic Latin Unicode块中的字母、数字和符号字符(前127个ASCII字符)转换为它们的等价ASCII字符(如果存在)。例如,过滤器将à更改为a。
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text": "açaí à la carte", "tokenizer": "keyword", "filter": ["asciifolding"] }
结果如下:
5.4.7 stop 词项过滤器
-
作用
该过滤器会将词项中的停用词删除。
如果没有自定义停用词,该过滤器会默认删除下列英文停用词:
a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with
除了英语,该过滤器还支持多种语言的预定义停止词列表,并且还可以将自己的停止词指定为数组或文件。
详细配置请查看官方文档
-
示例
这里只做简单示例。
// GET http://localhost:9200/_analyze // body内容 { "text": "a quick fox jumps over the lazy dog", "tokenizer": "standard", "filter": ["stop"] }
结果如下:
5.5 常见的内置分析器
Elasticsearch提供了一些内置的分析器供用户使用。
接下来将会介绍一些常用的内置分析器,更多详细内置分析器见官方文档
5.5.1 standard 分析器
-
作用
该分析器是默认分析器,如果没有指定分析器,则默认使用它。它提供了基于Unicode文本分割算法的分词功能,它删除大多数标点符号,会将词项转换为小写,并支持删除停止词。适用于大部分语言,但是对中文支持的不理想,会逐字拆分,按词切分。
-
组成
-
字符过滤器
无
-
分词器
standard 分词器
-
词项过滤器
lowercase词项过滤器
stop词项过滤器(默认不启用)
-
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I Like StEak !", "analyzer": "standard" }
结果如下:
5.5.2 simple 分析器
-
作用
该分析器在任何非字母字符处(如数字、空格、连字符和撇号)将文本分解为词项,并且会丢弃非字母字符、将大写改为小写。
-
组成
-
字符过滤器
无
-
分词器
lowercase 分词器
-
词项过滤器
无
-
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I Like &StEak 和西红柿 2333 !", "analyzer": "simple" }
结果如下:
5.5.3 stop 分析器
-
作用
该分析器与simple分析器相同,但增加了删除停用词的支持。
-
组成
-
字符过滤器
无
-
分词器
lowercase 分词器
-
词项过滤器
stop 词项过滤器
-
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I Like &StEak the 和西红柿 2333 !", "analyzer": "stop" }
结果如下:
5.5.4 pattern 分析器
-
作用
该分析器使用正则表达式将文本拆分为各个词项,,并将词项由大写转换为小写。正则表达式应该匹配分隔符,而不是词项本身。正则表达式默认为\W+(所有非单词字符)。
注意:这里使用的是Java正则表达式,如果正则表达式的性能较差,可能会导致stackoverflow异常。
-
组成
-
字符过滤器
无
-
分词器
pattern 分词器
-
词项过滤器
lowercase词项过滤器
stop词项过滤器(默认不启用)
-
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I,Like&StEak,the,和西红柿,2333 !", "analyzer":"pattern" }
结果如下:
5.5.5 whitespace 分析器
-
作用
该分析器会在遇到空白字符时,将文本分解为词项。
-
组成
-
字符过滤器
无
-
分词器
whitespace分词器
-
词项过滤器
无
-
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I Like &StEak the 和西红柿 2333 !", "analyzer":"whitespace" }
结果如下:
5.5.6 keyword 分析器
-
作用
该分析器是一个“noop”分析器,它将整个输入字符串作为单个词项返回。
-
组成
-
字符过滤器
无
-
分词器
keyword分词器
-
词项过滤器
无
-
-
示例
// GET http://localhost:9200/_analyze // body内容 { "text":"I Like &StEak the 和西红柿 2333 !", "analyzer":"keyword" }
结果如下:
5.6 自定义分析器
当Elasticsearch内置的分析器无法满足业务需求时,我们可以定义自定义分析器来实现业务需求。
自定义分析器也是由0或多个字符过滤器+一个分词器+0或多个词项过滤器组成的。
分析器常用配置参数如下:
参数名 | 含义 |
---|---|
type | 分析器类型。接受内置分析器类型。对于自定义分析器,使用custom或省略此参数。 |
tokenizer | 指定使用的分词器,可以是内置分词器或者自定义的分词器 |
char_filter | 指定使用的字符过滤器,可以是内置的或者自定义的,这里接收的是一个数组。 |
filter | 指定使用的词项过滤器,可以是内置的或者自定义的,这里接收的是一个数组。 |
下面我们将通过示例来讲解各个配置。
// PUT http://localhost:9200/custom-analyzer-test
// body内容
{
"settings": {
"analysis": {
"analyzer": { // 当前索引中定义的分析器
"my_analyzer": { // 自定义的分析器名称
"type":"custom", // 类型-自定义分析器
"tokenizer": "my_tokenizer", // 自己定义的分词器
"char_filter": ["&_to_and"], // 自定义的分析器使用的字符过滤器集合
"filter":["my_stopwords","lowercase"] // 自定义的分析器使用的词项过滤器集合
}
},
"char_filter": { // 当前索引中的字符过滤器集合
"&_to_and": { // 自定义字符过滤器的名称
"type": "mapping", // 字符过滤器类型为mapping
"mappings":["& => and"]
}
},
"tokenizer":{ // 当前索引中定义的分词器
"my_tokenizer":{
"type":"pattern",
"pattern":","
}
},
"filter":{ // 当前索引中定义的词项过滤器
"my_stopwords":{
"type":"stop",
"stopwords": [ "the","a"]
}
}
}
}
}
测试自定义分析器
// GET http://localhost:9200/custom-analyzer-test/_analyze
// body内容
{
"text":"I,Like & StEak,the,和西红柿,2333 !",
"analyzer":"my_analyzer"
}
结果如下:
该自定义分析器的工作流程图如下图所示。
5.7 中文分析器
5.7.1 Elasticsearch对于中文的分析支持
在Elasticsearch提供的分析器中,对于中文的支持不够友好,它无法自动识别中文中的词组,例如:学习,学校等。
例如,使用standard分析器对“我要去学校学习”进行分析。
// GET http://localhost:9200/_analyze
// body内容
{
"text":"我要去学校学习",
"analyzer":"standard"
}
结果如下:
由上面的结果可以看出,使用Elasticsearch提供的分析器,无法满足我们对于中文分词的需求。
因此,我们常常使用IK分词器进行扩展。
5.7.2 IK分词器简介
IK分词器是免费开源的java分词器,是目前比较流行的中文分词器之一,它简单、稳定,但如果想要特别好的效果,需要自行维护词库,支持自定义词典。
5.7.3 IK分词器-安装
-
下载地址
https://github.com/medcl/elasticsearch-analysis-ik
注意:要选择你的Elasticsearch对应版本的ik分词器
-
部署
将下载下来的IK分词器,解压在Elasticsearch安装目录的plugins子目录中。
解压完后,重启Elasticsearch即可。
注意:如果启动时报错如下:
java.lang.IllegalStateException: Could not load plugin descriptor for plugin directory [commons-codec-1.9.jar] at org.elasticsearch.plugins.PluginsService.readPluginBundle(PluginsService.java:403) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.findBundles(PluginsService.java:388) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.getPluginBundles(PluginsService.java:381) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.<init>(PluginsService.java:152) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.node.Node.<init>(Node.java:317) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.node.Node.<init>(Node.java:266) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap$5.<init>(Bootstrap.java:227) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:227) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:393) [elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:170) [elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:161) [elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) [elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:127) [elasticsearch-cli-7.8.0.jar:7.8.0] at org.elasticsearch.cli.Command.main(Command.java:90) [elasticsearch-cli-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:126) [elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:92) [elasticsearch-7.8.0.jar:7.8.0] Caused by: java.nio.file.NoSuchFileException: D:\Software\Work\elasticsearch-7.8.0\plugins\commons-codec-1.9.jar\plugin-descriptor.properties at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79) ~[?:?] at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) ~[?:?] at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) ~[?:?] at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:230) ~[?:?] at java.nio.file.Files.newByteChannel(Files.java:361) ~[?:1.8.0_91] at java.nio.file.Files.newByteChannel(Files.java:407) ~[?:1.8.0_91] at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:384) ~[?:1.8.0_91] at java.nio.file.Files.newInputStream(Files.java:152) ~[?:1.8.0_91] at org.elasticsearch.plugins.PluginInfo.readFromProperties(PluginInfo.java:156) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.readPluginBundle(PluginsService.java:400) ~[elasticsearch-7.8.0.jar:7.8.0] ... 15 more [2023-04-18T17:23:45,388][ERROR][o.e.b.ElasticsearchUncaughtExceptionHandler] [LAPTOP-AN1JMLBC] uncaught exception in thread [main] org.elasticsearch.bootstrap.StartupException: java.lang.IllegalStateException: Could not load plugin descriptor for plugin directory [commons-codec-1.9.jar] at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:174) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:161) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:127) ~[elasticsearch-cli-7.8.0.jar:7.8.0] at org.elasticsearch.cli.Command.main(Command.java:90) ~[elasticsearch-cli-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:126) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:92) ~[elasticsearch-7.8.0.jar:7.8.0] Caused by: java.lang.IllegalStateException: Could not load plugin descriptor for plugin directory [commons-codec-1.9.jar] at org.elasticsearch.plugins.PluginsService.readPluginBundle(PluginsService.java:403) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.findBundles(PluginsService.java:388) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.getPluginBundles(PluginsService.java:381) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.<init>(PluginsService.java:152) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.node.Node.<init>(Node.java:317) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.node.Node.<init>(Node.java:266) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap$5.<init>(Bootstrap.java:227) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:227) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:393) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:170) ~[elasticsearch-7.8.0.jar:7.8.0] ... 6 more Caused by: java.nio.file.NoSuchFileException: D:\Software\Work\elasticsearch-7.8.0\plugins\commons-codec-1.9.jar\plugin-descriptor.properties at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79) ~[?:?] at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) ~[?:?] at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) ~[?:?] at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:230) ~[?:?] at java.nio.file.Files.newByteChannel(Files.java:361) ~[?:1.8.0_91] at java.nio.file.Files.newByteChannel(Files.java:407) ~[?:1.8.0_91] at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:384) ~[?:1.8.0_91] at java.nio.file.Files.newInputStream(Files.java:152) ~[?:1.8.0_91] at org.elasticsearch.plugins.PluginInfo.readFromProperties(PluginInfo.java:156) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.readPluginBundle(PluginsService.java:400) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.findBundles(PluginsService.java:388) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.getPluginBundles(PluginsService.java:381) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.plugins.PluginsService.<init>(PluginsService.java:152) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.node.Node.<init>(Node.java:317) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.node.Node.<init>(Node.java:266) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap$5.<init>(Bootstrap.java:227) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:227) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:393) ~[elasticsearch-7.8.0.jar:7.8.0] at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:170) ~[elasticsearch-7.8.0.jar:7.8.0] ... 6 more
可以在plugins建一层子目录ik,然后再把解压后的文件都放在ik子目录中
5.7.4 IK分词器配置讲解
在ik分词器的config目录中有一些ik分词器自带的配置。
各个配置的含义如下表所示
配置文件名 | 含义 |
---|---|
main.dic | 主词库 |
preposition.dic | 语气词(也、而、但等) |
stopword.dic | 英文停用词 |
quantifier.dic | 计量单位词 |
suffix.dic | 后缀词(省、市、所等) |
surname.dic | 百家姓词库 |
extra_main.dic | 扩展的主词库 |
extra_single_word.dic、extra_single_word_full.dic、extra_single_word_low_freq.dic | 扩展的单字词库 |
extra_stopword.dic | 扩展的停用词库 |
IKAnalyzer.cfg.xml | IK分词器配置文件 |
5.7.5 IK分词器-内置分析器
在IK分词器中内置了两种分析器:ik_max_word和ik_smart
-
ik_max_word
将文本做最细粒度的拆分
-
ik_smart
将文本做最粗粒度的拆分
-
通过示例对比两种分析器
ik_max_word
// GET http://localhost:9200/_analyze // body内容 { "text":"我要去学校学习", "analyzer":"ik_max_word" }
结果如下:
ik_smart
// GET http://localhost:9200/_analyze // body内容 { "text":"我要去学校学习", "analyzer":"ik_smart" }
结果如下:
5.7.6 IK分词器-扩展词组
当我们需要让某些字组成一个特定词组时,我们可以通过扩展词组的方式,进行自定义词组的扩展。
例如,“英雄联盟”,本身不是一个特定的词组,它会被分词器拆分。
下面我们来进行测试
// GET http://localhost:9200/_analyze
// body内容
{
"text":"英雄联盟",
"analyzer":"ik_smart"
}
结果如下:
可以看到,“英雄联盟”会被拆分成“英雄”和“联盟”两个词,这并不是我们想要的,我们想要“英雄联盟”被当做一个词项。
此时,我们可以通过在ik分词器的config目录中的IKAnalyzer.cfg.xml文件里进行配置。
<?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"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
</properties>
我们做如下配置
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">mydic.dic</entry>
</properties>
在config目录中创建mydic.dic文件,把"英雄联盟"写入该文件,随后重启Elasticsearch即可。
下面我们再一次进行测试
// GET http://localhost:9200/_analyze
// body内容
{
"text":"英雄联盟",
"analyzer":"ik_smart"
}
结果如下:
可以看到,"英雄联盟"被当做了一个词项,不会被拆分了。
参考