理解Elasticsearch分析器 analyzer

4,516 阅读5分钟

理解Elasticsearch分析器

本文尝试将ES的分析器部分进行分析。分析器在ES中被定义为analyzer。学习ES的分析器,能够帮助我们较好地了解搜索引擎对文本数据的索引过程,全文本搜索的基本原理。同时有助于我们定义自己的分析器来满足特定的业务需求,提升搜索准确率。

本文主要参考了Elasticsearch reference关于analyzer的章节。由于Elasticsearch reference都是英文,因此希望通过本文给读者提供一种阅读关于 ES Analyzer Reference 的框架和思路。

另外:本文基于elasticsearch 7.9 来做测试验证,不包含ES的安装部署过程。所有演示请求操作都在kibana 的dev-tool上完成提交。

1.基本简介:

首先要区分 分析器(analyzer)和分词器(tokenizer) 的区别,在ES中,分析器不等于分词器,分词器只是分析器的一部分。 可以通过下面的公式来进行区分:

analyzer = [char_filter] + tokenizer + [token filter]

一个ES分析器包含下面三部分:

  • char filter: 对输入的文本字符进行第一步处理,如去除html标签(html_strip),将表情字符转换成英文单词(mapping)等。ES内置的字符处理器可参考 char filter reference

  • tokenizer: 对文本进行分词操作,如按照空格分词(whitespace),标准分词器(standard)等,切分好的单元在ES(Lucene?)中被定义为 token。ES内置的分词器可参考 tokenizer reference

  • filter (token filter): 对一个token集合的元素做过滤和转换(修改),删除等操作。例如可以将whitespace tokenizer切割的单元转换为词干形式(driven-->drive),统一转换为小写形式(lowercase),过滤掉一些停用词(stop)等。ES有丰富的内置的token filter,详细可以参考这里 token filter reference 。 token经过 filter处理之后的结果被定义为:term

一个 text类型字段,需要经过ES的分析器处理后才能写入。 analyzer的处理过程如下:

es_analyzer_flow.png

2.ES 内置analyzer

ES 内置了很多analyzer,详细请参考这里 ES build-in analyzer reference 。下面举例介绍部分内置的分词器 standard analyzer。ES官方文档对每种analyzer的介绍都很详细。其他内置的analyzer都可以按照下面的思路去分析和理解其官方文档的内容。

standard analyzer

习惯被翻译为标准分析器,详细介绍可以参考ES官方介绍 standard analyzer。定义公式:

standard analyzer = [] + standard tokenizer + ["lower case token filter", "stop token filter" ]

从上面的公式,我们可以知道standard analyzer有以下特点:

  1. 没有配置char_filter,因此不会去除html标签等。char filter阶段不做任何的事情。见下面的例子。
  2. 使用standard tokenizer来切词,过滤掉标点符号,按照空格切词(中文会按照字进行切分),但是 he's 这种会当做一个token处理
  3. token filter阶段,将token转换成小写,然后过滤掉停用词。

下面测试standard analyzer的分析结果:

POST _analyze
{
  "analyzer": "standard",
  "text": "<html> Hello world !!! he's </html>"
}

//返回结果:发现html标签没有被去掉,仅仅去掉了符号<>,he's 被当做一个独立的token, 标点感叹号 !被去掉,大写字母都转成小写
{
  "tokens" : [
    {
      "token" : "html",
      "start_offset" : 1,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "hello",
      "start_offset" : 7,
      "end_offset" : 12,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "world",
      "start_offset" : 13,
      "end_offset" : 18,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "he's",
      "start_offset" : 23,
      "end_offset" : 27,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "html",
      "start_offset" : 31,
      "end_offset" : 35,
      "type" : "<ALPHANUM>",
      "position" : 4
    }
  ]
}

3. _analyze API

ES 提供了分析器api接口给用户做相关调试:

POST _analyze

通过该api,可以帮助我们对analyzer的char filter, tokenizer , token filter 建立很好的理解,帮助我们调试属于自己的分析器

  1. 调试analyzer, 指定analyzer即可
POST _analyze
{
  "text": "<html> hello world !!! He's  </html>",
  "analyzer": "standard"
}
  1. 如此类推,如果要调试 char_filter/tokenizer/token filter ,直接指定即可,三者可以同时出现或者部分出现。
POST _analyze
{
  "text": "<html> hello world !!! \n He's a student </html>",
  "char_filter": ["html_strip"], 
  "tokenizer": "whitespace", 
  "filter": [{
   "type":"stop",
   "stopwords":["a","the","is"]
   },
   "lowercase"
 ]
}

通过该api进行调试,达到我们自己的业务需求后就可以将这种组合关系保存为自定义的analyzer。

4.自定义analyzer

如果ES内置的analyzer无法满足自己的业务需求,我们可以通过自定义analyzer。在新建索引的时候,我们可以通过选定特定的 char_filter,tokenizer和filter(token filter)组合来定义自己的分析器。然后在配置索引的mapping时可以给特定的字段选择自定义analyzer,以满足自己的业务需求。

下面举例定义了一个自己的analyzer,该analyzer有以下特点:

    1. char_filter,使用了自定义的char_filter:
    • my_html_strip: 过滤html标签
    • my_punctuation_mapping: 对特定的标点符号进行转换, 转换规则为: * => _ ,= => ~
    1. tokenizer: 使用空格进行切词(whitespace)
    1. filter(token filter): 自定义停用词filter,过滤停用词 "is", "a", "the"
PUT analyzer_demo
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer":{            //自定义的analyzer
          "type":"custom",
          "char_filter":[           //chart_filter序列,可以是自定义的,也可以是ES内置的
              "my_html_strip",
              "my_punctuation_mapping"
            ],
          "tokenizer": "my_tokenizer",   //分词器,只能写一个
          "filter": ["my_stop_token_filter"]    //token filter序列,可以自定义的也可以是ES内置的
        }
      },
      //下面分别自定义自己的char filter,tokenizer ,token filter
      "char_filter": {
        "my_punctuation_mapping":{
          "type":"mapping",
          "mappings":["* => _","= => ~"]
        },
        "my_html_strip":{
          "type":"html_strip"
        }
      },
      "tokenizer": {
        "my_tokenizer":{
          "type":"whitespace"
        }
      },
      "filter": {
        "my_stop_token_filter":{
          "type":"stop",
          "ignore_case":true,
          "stopwords": ["is","a","the"]
        }
       
      }
    }
  },
  "mappings": {
    "properties": {
      "name":{
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

!!!! 需要特别注意的地方是,虽然在analyzer中,chart filter和token filter都是数组,但是它的key不是 char_filters 和 filters,而是 char_filter 和 filter,千万不能写错,目前版本ES在写错成 char_filter和filter为复数形式的情况下不会报错,但是会导致测试不是我们所预期的,我踩了这个坑。

下面是测试结果:

GET analyzer_demo/_analyze
{
  "text": "<html> Hello World!! 123-456 789*123 , he is a student  </html>",
  "analyzer": "my_analyzer"
}

{
  "tokens" : [
    {
      "token" : "Hello",
      "start_offset" : 7,
      "end_offset" : 12,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "World!!",
      "start_offset" : 13,
      "end_offset" : 20,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "123-456",
      "start_offset" : 21,
      "end_offset" : 28,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "789_123",
      "start_offset" : 29,
      "end_offset" : 36,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : ",",
      "start_offset" : 37,
      "end_offset" : 38,
      "type" : "word",
      "position" : 4
    },
    {
      "token" : "he",
      "start_offset" : 39,
      "end_offset" : 41,
      "type" : "word",
      "position" : 5
    },
    {
      "token" : "student",
      "start_offset" : 47,
      "end_offset" : 54,
      "type" : "word",
      "position" : 8
    }
  ]
}