ElasticSearch7.9-自定义分词器并应用
背景
ES默认的分词器对英文是以词为单位进行切分,中文以字为单位进行切分。
若某字段值为“apple直营店”,以“app”为关键字进行match_phrase操作,无法匹配出数据,所以需要自定义分词器,将需要搜索的字段以字符为单位进行匹配。
步骤
创建自定义分词器
在创建index时,在settings.analysis中创建分词器。TIP
- settings.analysis.tokenizer用于指定使用的tokenizer,因standard tokenizer可以较好的进行分词,免去处理各种符号的麻烦,所以此处type设置为standard;如不指定max_token_length,则每个词的最大长度会默认为255,我们的需求是按单个字符分词,所以需要设置为1。
- settings.analysis.analyzer是字段中需要引用的分词器,tokenizer只是analyzer中的一个设置项。
如下配置片段中,定义了一个名为“single_character_tokenizer”的tokenizer,并定义了一个名为“single_character_analyzer”的分词器,该分词器使用“single_character_tokenizer”作为tokenizer。
XMLHttpRequestPUT es_test_log { "settings": { "analysis": { "analyzer": { "single_character_analyzer": { "tokenizer": "single_character_tokenizer" } }, "tokenizer": { "single_character_tokenizer": { "type": "standard", "max_token_length": 1 } } } } }
在mapping阶段,把analyzer应用到相关字段
实际操作中发现,keyword类型的字段不能设置analyzer属性,否则会报不支持参数的错,导致mapping失败。需要将相关字段设置为text类型。
而使用text类型,又无法使用聚合查询,所以需要再在字段内部设置keyword类型的字段,用来聚合查询等操作。
index_options需要设置为positions,否则match_phrase操作会报错,提示索引期间没有生成position信息。(该问题是因为将相关字段设置为keyword类型,match_phrase时指定了分词器引起。实际上index_options默认值即为positions)
如下配置片段中,为testName字段指定了“single_character_analyzer”分词器,并在内部添加了一个类型为keyword的名为“keyword”的字段,index_options字段值为positions时可有可无。JSON{ "properties": { "testName": { "type": "text", "analyzer": "single_character_analyzer", "index_options": "positions", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } }
验证match_phrase操作
如下查询,在es_test_log的testName字段中模糊搜索“test_name1”,并指定“single_character_analyzer”分词器。
若在mapping阶段已指定analyzer,查询时可不带analyzer参数。XMLHttpRequestGET es_test_log/_search { "query": { "match_phrase": { "testName": { "query": "test_name1", "slop": 0, "analyzer": "single_character_analyzer" } } } }
验证聚合查询
如下查询,在es_test_log中,通过testName进行聚合查询,因聚合查询只能通过keyword类型字段进行查询,所以mapping时,对text类型字段建立了keyword类型的内嵌字段“keyword”。XMLHttpRequestPOST es_test_log/_search { "size": 0, "query": { "range": { "createTime": { "gte": 1103260472836, "lte": 1903260474019 } } }, "aggs": { "test_agg": { "terms": { "field": "testName.keyword" }, "aggs": { "max_create_time":{ "max": { "field": "createTime" } }, "test_name_count":{ "value_count": { "field": "testName.keyword" } } } } } }
至此,自定义分词器并应用成功。余下部分可忽略。
调试命令
查询某个document的分词结果
格式
GET /index名/type名/id/_termvectors?fields=field名
示例
GET /es_test_log/_doc/W1yJaXUBWzZcDFlTQTaE/_termvectors?fields=testName
ES版本升级后,要注意type名,8版本计划去掉type。
查询指定分词器对某段文本的分词结果
格式
POST 分词器所在index名/_analyze
{
"analyzer": "分词器名",
"text": "需要分词的文本"
}
示例
POST es_test_log/_analyze
{
"analyzer": "single_character_analyzer",
"text": "test_name1"
}
查看执行计划
因没有找到相关的文档,所以该命令在实际调试时并没有起到太大的作用。
格式
GET index名/_validate/query?explain
{
"query": {
"match_phrase": {
"field名": {
"query": "查询的关键字",
"slop": 0,
"analyzer": "自定义的analyzer名"
}
}
}
}
示例
GET es_test_log/_validate/query?explain
{
"query": {
"match_phrase": {
"testName": {
"query": "test_name1",
"slop": 0,
"analyzer": "single_character_analyzer"
}
}
}
}
查看index信息
格式
GET index名
示例
GET es_test_log
调试过程
在定义分词器阶段,先是尝试了使用正则表达式获取单个字符,但是处理不掉空格。配置如下
PUT my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "simple_pattern",
"pattern": "[^\\W\\s]|[\\u4e00-\\u9fa5]"
}
}
}
}
}
因standard tokenizer分词效果较好,可将空格、各种符号过滤掉,但默认以词为单位进行分割,所以看一下配置文件,是否能参考。
结果就发现了“max_token_length”参数。
尝试将该参数改为1,使用相关查询进行验证,达到了预期的效果,所以抛弃正则分词,改用standard。
分词器定义成功后,填充数据,指定分词器进行match_phrase搜索。
第一次:查询字段为keyword类型,match_phrase报错,大致意思是没有position信息(match_phrase需要position信息);
第二次:在mapping期间,给查询字段添加"index_options": "positions"参数,提示The [keyword] field does not support positions(keyword类型字段不支持positions);
第三次:将查询字段改为text类型,未报错,但是查询不出结果(仅在查询时引用了分词器,mapping时未对查询字段设置分词器);
第四次:查到Index and search analysis和Specify an analyzer 两段文档,在mapping期间,为field指定analyzer;
在第四次之后,可按照预期查询出结果。但为了验证字段类型和分词器之间的可用关系,又做了以下尝试:
- 查询字段类型改为keyword,并指定分词器。mapping失败,keyword类型字段不支持analyzer配置。
- 查询字段类型改为text,且不内嵌keyword类型字段,并指定分词器。match_phrase查询正常,聚合查询无结果。
- 查询字段类型设置为text,且内嵌keyword类型字段,并指定分词器。match_phrase查询正常,聚合查询正常。