一、ES搜索与Analyzer
1.ES搜索过程
为了搞清楚这个问题,我们需要提前搞清楚ES搜索的过程,ElasticSearch的这个能力是由Analyzer来实现的。ES中一个Analyzer处理搜索的过程如下:
从这个处理流程可以看到,在token filter会为词条增加一些同义词,这就是ES可以支持同义词搜索的原因所在。
2.Analyzer(解析器)
1)Analyzer组成
Analyzer由0个或多个char_filter,1个tokenizer,0个或多个token filter组成。
char_filter用于分词前对原搜索的句子进行处理,如去除HTML标签。
tokenizer用于将搜索的句子分成多个词组,如将一段话根据分词器或者空格拆分成多个词等。
token filter用于处理tokenizer输出的词组,常见的操作有:删除、修改增加某些词,例如:去掉a、the词组,大写转小写,增加同义词。
Analyzer的配置说明:
处理器 | 描述 |
---|---|
tokenizer | 通用的或者注册的tokenizer. |
filter | 通用的或者注册的 token filters. |
char_filter | 通用的或者注册的 character filters. |
position_increment_gap | 距离查询时,最大允许查询的距离,默认是100 |
2)Analyzer与search_analyzer的区别
ES中主要有两种情况会用分析器:一是插入文档时,将text类型的字段做分词然后插入倒排索引,二是查询时,先对要查询的text类型的输入做分词,再去倒排索引搜索。使用的具体规则是:创建索引时,只看字段有没有定义analyzer,有定义的话就用定义的,没定义就用ES预设的;在查询时,先看字段有没有定义search_analyzer,如果没有定义,去看有没有analyzer,再没有定义才会去使用ES预设的。
2.CharFilter
elasticearch只提供了三种字符过滤器:
1)HTML Strip Char Filter(HTML字符过滤器)
可以去除HTML标签,例如将
I'
Love cat
示例代码:
{
"tokenizer":"keyword",
"char_filter":[
"html_strip"
],
"text":"<p>I' <br>Love</br> cat</p>"
}
结果为:
{
"tokenizer":"keyword",
"char_filter":[
"html_strip"
],
"text":"I Love cat"
}
2)Mapping Char Filter(映射字符过滤器)
可以替换查询字符串的内容,例如将“特朗普”转变为:TTT。
{
"settings":{
"analysis":{
"analyzer":{
"my_analyzer":{
"tokenizer":"keyword",
"char_filter":[
"my_char_filter"
]
}
},
"char_filter":{
"my_char_filter":{
"type":"mapping",
"mappings":[
"特朗普=> TTT "
]
}
}
}
}
}
创建索引或者搜索的时候将会将“特朗普”转换成“TTT”。
3)Pattern Replace Char Filter(模式替换过滤器)
可以使用正则表达式匹配并替换字符串中的字符。具体能力看正则表达式,具体示例省略。
3.tokenizer
接下来介绍几种常见的分词器。
1)standard tokenizer (标准分词器)
标准类型的tokenizer,对英语等欧洲语言非常友好,支持Unicode。属性:
属性 | 说明 |
---|---|
max_token_length | 最大的token集合,即经过tokenizer过后得到的结果集的最大值。如果token的长度超过了设置的长度,将会继续分,默认255 |
2)NGram Tokenizer(连词分词器)
如果词的长度大于最短词长度则分词,依次分成最小长度递进到最大长度的词。例如:min_gram=2, max_gram=3下王者荣耀的分词结果为:王者、王者荣、者荣耀、荣耀。
属性:
设置 | 说明 | Default value |
---|---|---|
min_gram | 分词后词语的最小长度 | 1 |
max_gram | 分词后词语的最大长度 | 2 |
token_chars | 设置分词的形式 | [](Keep all characters)letter(单词),digit(数字),whitespace(空白),punctuation(标点,如!),symbol(符号,如&) |
3)Edge NGram tokenizer(自动补全)
和NGramTokenizer非常相似,不过支持自动补全功能。
二、中文分词
1.场景
假设ik_demo索引中有以下记录,如下表格所示
_index | _type | _id | _score | message |
---|---|---|---|---|
ik_demo | _doc | 1 | 1 | 我爱中华人民共和国 |
ik_demo | _doc | 2 | 1 | 我来自中国湖北枣阳 |
按照这个示例,“国”在不同上下文中是不同的,第二句“中国”是一个词,单独搜索“国”的时候应该返回此记录。
2.elasticsearch-analysis-ik
elasticsearch需要先安装分词器才能支持分词功能,这里使用的是elasticsearch-analysis-ik,地址:https://github.com/medcl/elasticsearch-analysis-ik
安装ik:
./bin/elasticsearch-plugin installhttps://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.3.2/elasticsearch-analysis-ik-7.3.2.zip
注意:7.3.2为使用的elasticsearch版本;
3.设置mapping
curl -XPUT 'http://localhost:9200/ik_demo/_mapping?pretty' -H 'Content-Type: application/json' -d '
{
"properties" : {
"message" : {
"type" : "text",
"analyzer" : "ik_max_word",
"search_analyzer" : "ik_smart",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}'
4.添加数据
curl -XPUT 'http://localhost:9200/ik_demo/_doc/1?pretty' -H 'Content-Type: application/json' -d '
{ "message": "我爱中华人民共和国"}'
curl -XPUT 'http://localhost:9200/ik_demo/_doc/2?pretty' -H 'Content-Type: application/json' -d '
{"message": "我来自中国湖北枣阳"}'
5.搜索
分词过程中会将中国作为一个词,所以单独搜索“国”的时候,将无法搜索到带有“我来自中国湖北枣阳”这个记录。
curl -XGET 'http://localhost:9200/ik_demo/_search?pretty=true' -H 'Content-Type: application/json' -d '
{"query":{"match":{"message":"国"}}}'
返回结果:
{
"took":3,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":{
"value":1,
"relation":"eq"
},
"max_score":0.2876821,
"hits":[
{
"_index":"ik_demo",
"_type":"_doc",
"_id":"1",
"_score":0.2876821,
"_source":{
"message":"我爱中华人民共和国"
}
}
]
}
}
这里仅简单介绍使用,具体分词内容可以参考:https://github.com/medcl/elasticsearch-analysis-ik
三、近义词、同义词搜索
1.场景
假设synonym_demo索引中有以下记录,如下表格所示
_index | _type | _id | _score | message |
---|---|---|---|---|
synonym_demo | _doc | 1 | 1 | 我喜欢猫 |
synonym_demo | _doc | 2 | 1 | 我喜欢cat |
synonym_demo | _doc | 3 | 1 | 我喜欢狗 |
猫和cat其实是同一个意思,如果想搜索猫的时候,将【1、2】记录都搜索出来,应该如何处理呢?
2.elasticsearch-dynamic-synonym
安装同义词插件,这里使用的是elasticsearch-dynamic-synonym,地址:https://github.com/ginobefun/elasticsearch-dynamic-synonym
安装:
git clone https://github.com/ginobefun/elasticsearch-dynamic-synonym.git
mvn clean install -DskipTests
将target/releases/elasticsearch-dynamic-synonym-.zip解压到ES_HOME/plugin/dynamic-synonym目录中。
修改plugin-descriptor.properties,注释掉site、jvm、isolated,并且将elasticsearch.version改成elasticsearch的版本。如下所示:
site=${elasticsearch.plugin.site}
jvm=true
isolated=${elasticsearch.plugin.isolated}
elasticsearch.version=7.3.2
3.创建索引
在ES_HOME/config/analysis/synonyms.txt中添加同义词:猫,cat
创建索引:
curl -XPUT 'http://localhost:9200/synonym_demo?pretty' -H 'Content-Type: application/json' -d '
{
"settings": {
"analysis": {
"analyzer": {
"my_dynamic_synonym": {
"type": "custom",
"tokenizer": "whitespace",
"filter": ["my_synonym"]
}
},
"filter": {
"my_synonym" : {
"type" : "synonym",
"expand": true,
"ignore_case": true,
"synonyms_path" : "analysis/synonyms.txt"
}
}
}
},
"mappings":{
"properties" : {
"message" : {
"type" : "text",
"analyzer" : "ik_max_word",
"search_analyzer" : "my_dynamic_synonym",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}'
4.添加数据
curl -XPUT 'http://localhost:9200/synonym_demo/_doc/1?pretty' -H 'Content-Type: application/json' -d '{ "message": "我喜欢猫" }'
curl -XPUT 'http://localhost:9200/synonym_demo/_doc/2?pretty' -H 'Content-Type: application/json' -d '{ "message": "我喜欢cat" }'
curl -XPUT 'http://localhost:9200/synonym_demo/_doc/3?pretty' -H 'Content-Type: application/json' -d '{ "message": "我喜欢狗"}'
5.搜索
因为猫、cat是同义词,所以搜索cat的时候,猫也应该会返回。
curl -XGET 'http://localhost:9200/synonym_demo/_search?pretty=true' -H 'Content-Type: application/json' -d '
{"query":{"match":{"message":"cat"}}}'
返回结果:
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.2039728,
"hits" : [
{
"_index" : "synonym_demo",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.2039728,
"_source" : {
"message" : "我喜欢猫"
}
},
{
"_index" : "synonym_demo",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.2039728,
"_source" : {
"message" : "我喜欢cat"
}
}
]
}
}
四、参考文档
search_analyzer:
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-analyzer.html
Custom Analyzer:
https://www.elastic.co/guide/en/elasticsearch/reference/2.1/analysis-custom-analyzer.html
elasticsearch-dynamic-synonym: