Skip to content

ElasticSearch7.9-自定义分词器并应用

背景

ES默认的分词器对英文是以词为单位进行切分,中文以字为单位进行切分。
若某字段值为“apple直营店”,以“app”为关键字进行match_phrase操作,无法匹配出数据,所以需要自定义分词器,将需要搜索的字段以字符为单位进行匹配。

步骤

  1. 创建自定义分词器
    在创建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。

    XMLHttpRequest
    PUT es_test_log
    {
        "settings": {
            "analysis": {
                "analyzer": {
                    "single_character_analyzer": {
                    "tokenizer": "single_character_tokenizer"
                    }
                },
                "tokenizer": {
                    "single_character_tokenizer": {
                        "type": "standard",
                        "max_token_length": 1
                    }
                }
            }
        }
    }
  2. 在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
                    }
                }
            }
        }
    }
  3. 验证match_phrase操作
    如下查询,在es_test_log的testName字段中模糊搜索“test_name1”,并指定“single_character_analyzer”分词器。
    若在mapping阶段已指定analyzer,查询时可不带analyzer参数。

    XMLHttpRequest
    GET es_test_log/_search
    {
        "query": {
            "match_phrase": {
                "testName": {
                    "query": "test_name1",
                    "slop": 0,
                    "analyzer": "single_character_analyzer"
                }
            }
        }
    }
  4. 验证聚合查询
    如下查询,在es_test_log中,通过testName进行聚合查询,因聚合查询只能通过keyword类型字段进行查询,所以mapping时,对text类型字段建立了keyword类型的内嵌字段“keyword”。

    XMLHttpRequest
    POST 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的分词结果

格式

request
GET /index名/type名/id/_termvectors?fields=field名

示例

request
GET /es_test_log/_doc/W1yJaXUBWzZcDFlTQTaE/_termvectors?fields=testName

ES版本升级后,要注意type名,8版本计划去掉type。

查询指定分词器对某段文本的分词结果

格式

request
POST 分词器所在index名/_analyze
{
  "analyzer": "分词器名",
  "text":     "需要分词的文本"
}

示例

request
POST es_test_log/_analyze
{
  "analyzer": "single_character_analyzer",
  "text":     "test_name1"
}

查看执行计划

因没有找到相关的文档,所以该命令在实际调试时并没有起到太大的作用。
格式

request
GET index名/_validate/query?explain
{
  "query": {
    "match_phrase": {
      "field名": {
        "query": "查询的关键字",
        "slop": 0,
        "analyzer": "自定义的analyzer名"
      }
    }
  }
}

示例

request
GET es_test_log/_validate/query?explain
{
  "query": {
    "match_phrase": {
      "testName": {
        "query": "test_name1",
        "slop": 0,
        "analyzer": "single_character_analyzer"
      }
    }
  }
}

查看index信息

格式

request
GET index名

示例

request
GET es_test_log

调试过程

在定义分词器阶段,先是尝试了使用正则表达式获取单个字符,但是处理不掉空格。配置如下

request
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;
在第四次之后,可按照预期查询出结果。但为了验证字段类型和分词器之间的可用关系,又做了以下尝试:

  1. 查询字段类型改为keyword,并指定分词器。mapping失败,keyword类型字段不支持analyzer配置。
  2. 查询字段类型改为text,且不内嵌keyword类型字段,并指定分词器。match_phrase查询正常,聚合查询无结果。
  3. 查询字段类型设置为text,且内嵌keyword类型字段,并指定分词器。match_phrase查询正常,聚合查询正常。