前言

我们都知道,ES的核心功能之一便是全文本搜索,这种全文本搜索,可以返回相关的结果,而非精确的匹配结果。

这其中文本分析Analysis发挥了重要的作用,为什么索引一个文本字段如“My Name is Old Nico”,当搜索词组“My Nico”时可以搜索的到呢?

本文演示ES版本为8.6.2


概念

Text Analysis(文本分析)就是将一个非结构化的复杂文本(如商品描述)转换成结构化的词项的过程,也叫分词

分词是通过分词器Analyzer实现的。

分词器总共分为3个部分,不管是ES内置的还是自定义的,如下图所示:

  • Character filters
    输入是最原始的文本,进行处理,例如去除html等。它可以没有或者有多个,按顺序执行。

  • Tokenizer
    经过Character filters后的文本,按照规则进行分词,分成一个一个的Token(通常是单独的词),同时输出每个Token的起始位置和长度。每个分词器有一个唯一的Tokenizer。
    例如Standard Tokenizer、Letter Tokenizer、Whitespace Tokenizer等

  • Token filters
    经过Tokenizer拿到一系列的Token后,再通过Token filters进行Token的修改,例如移除英文停用词(is a等)、大写字母转小写、增加同义词等。它可以没有或者有多个,按顺序执行。


内置分词器

ES提供了可以适用大多数场景的内置分词器,可以很方便的使用它们,包括如下:

  • Standard Analyzer:标准的分词器按Unicode文本分割算法进行分割源文本,同时会移除标点符号、停用符,并将大写字母转成小写。也是默认分词器
  • Simple Analyzer:按照非字母字符进行分词,例如数字、空格、连接符等,并进行小写处理。
  • Whitespace Analyzer:按照空格进行切分,不进行小写处理。
  • Stop Analyzer:类似Simple分词器,同时过滤英文停用符(is a等,更多停用符),并进行小写处理。
  • Keyword Analyzer:不分词,直接将原文本输出。
  • Pattern Analyzer:正则表达式分词,默认 \W+(所有非文本字符),并进行小写处理。
  • Language Analyzers:提供了30多种常见语言的分词器,例如english。
  • Fingerprint Analyzer:使用了一种指纹识别算法进行分词,会进行去重。

特别说明Standard Analyzer

其它分词器详细说明可以查看官方文档>

Standard Analyzer包括:
Tokenizer:Standard Tokenizer
Token Filters:Lower Case Token Filter(小写处理)、Stop Token Filter (默认不生效)

接下来使用 _analyze api 进行测试,如下:

POST http://127.0.0.1:9200/_analyze
{
    "analyzer":"standard",
    "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone"
}

使用standard分词器进行分析

结果如下:

{
	"tokens": [
		{
			"token": "the",
			"start_offset": 0,
			"end_offset": 3,
			"type": "<ALPHANUM>",
			"position": 0
		},
		{
			"token": "2",
			"start_offset": 4,
			"end_offset": 5,
			"type": "<NUM>",
			"position": 1
		},
		{
			"token": "quick",
			"start_offset": 6,
			"end_offset": 11,
			"type": "<ALPHANUM>",
			"position": 2
		},
		{
			"token": "brown",
			"start_offset": 12,
			"end_offset": 17,
			"type": "<ALPHANUM>",
			"position": 3
		},
		{
			"token": "foxes",
			"start_offset": 18,
			"end_offset": 23,
			"type": "<ALPHANUM>",
			"position": 4
		},
		{
			"token": "jumped",
			"start_offset": 24,
			"end_offset": 30,
			"type": "<ALPHANUM>",
			"position": 5
		},
		{
			"token": "over",
			"start_offset": 31,
			"end_offset": 35,
			"type": "<ALPHANUM>",
			"position": 6
		},
		{
			"token": "the",
			"start_offset": 36,
			"end_offset": 39,
			"type": "<ALPHANUM>",
			"position": 7
		},
		{
			"token": "lazy",
			"start_offset": 40,
			"end_offset": 44,
			"type": "<ALPHANUM>",
			"position": 8
		},
		{
			"token": "dog's",
			"start_offset": 45,
			"end_offset": 50,
			"type": "<ALPHANUM>",
			"position": 9
		},
		{
			"token": "bone",
			"start_offset": 51,
			"end_offset": 55,
			"type": "<ALPHANUM>",
			"position": 10
		}
	]
}

可以看到文本经过 standard 分词器分词后,Token分别为:[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog’s, bone ]

我们也可以对 standard 进行如下特别配置:

  • max_token_length:可以指定Token的最大长度。
  • stopwords:配置停用词,默认缺省“none”。
  • stopwords_path:指定一个包含停用词的文件路径。

测试如下:
创建索引 my_standard_index

PUT http://127.0.0.1:9200/my_standard_index

{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_english_analyzer": {
          "type": "standard",
          "max_token_length": 5,
          "stopwords": "_english_"
        }
      }
    }
  }
}

指定了token的最大长度为5;过滤停用符stopwords使用英文语言的。

特别说明,默认情况下,索引文档不指定定义的分词器,则不会生效;如果想要生效,需要指定mapping的text字段应用定义的analyzer,那么在搜索和索引时,便会使用该分词器,默认是standard分词器。

测试配置的分词器:

POST http://127.0.0.1:9200/my_standard_index/_analyze

{
  "analyzer": "my_english_analyzer",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

结果:

{
	"tokens": [
		{
			"token": "2",
			"start_offset": 4,
			"end_offset": 5,
			"type": "<NUM>",
			"position": 1
		},
		{
			"token": "quick",
			"start_offset": 6,
			"end_offset": 11,
			"type": "<ALPHANUM>",
			"position": 2
		},
		{
			"token": "brown",
			"start_offset": 12,
			"end_offset": 17,
			"type": "<ALPHANUM>",
			"position": 3
		},
		{
			"token": "foxes",
			"start_offset": 18,
			"end_offset": 23,
			"type": "<ALPHANUM>",
			"position": 4
		},
		{
			"token": "jumpe",
			"start_offset": 24,
			"end_offset": 29,
			"type": "<ALPHANUM>",
			"position": 5
		},
		{
			"token": "d",
			"start_offset": 29,
			"end_offset": 30,
			"type": "<ALPHANUM>",
			"position": 6
		},
		{
			"token": "over",
			"start_offset": 31,
			"end_offset": 35,
			"type": "<ALPHANUM>",
			"position": 7
		},
		{
			"token": "lazy",
			"start_offset": 40,
			"end_offset": 44,
			"type": "<ALPHANUM>",
			"position": 9
		},
		{
			"token": "dog's",
			"start_offset": 45,
			"end_offset": 50,
			"type": "<ALPHANUM>",
			"position": 10
		},
		{
			"token": "bone",
			"start_offset": 51,
			"end_offset": 55,
			"type": "<ALPHANUM>",
			"position": 11
		}
	]
}

相比上面没有配置的Standard分词器拆分的Token,The没有了;jumped超过5个字符,截掉了d字符。


自定义分词器

自定义

如果ES内建的分词器无法满足需求时,需要自定义一个分词器,自定义分词器同样需要3个部分
1、指定0或多个Character filters
2、必须确定1个Tokenizer
3、指定0或多个Token filters

示例如下:

PUT http://127.0.0.1:9200/my_custom_index

{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer": {
          "type": "custom", 
          "tokenizer": "standard",
          "char_filter": [
            "html_strip"
          ],
          "filter": [
            "lowercase",
            "stop"
          ]
        }
      }
    }
  }
}

type指定custom表示定制化,也可以指定为ES内建的分词器如standard;char_filter为过滤html标签;tokenizer为standard;filter指定Token filters为转小写并过滤掉英文停用词。

测试自定义分词器:

POST http://127.0.0.1:9200/my_custom_index/_analyze

{
  "analyzer": "my_custom_analyzer",
  "text": "The is a Big city</br>"
}

结果如下:

{
	"tokens": [
		{
			"token": "big",
			"start_offset": 9,
			"end_offset": 12,
			"type": "<ALPHANUM>",
			"position": 3
		},
		{
			"token": "city",
			"start_offset": 13,
			"end_offset": 17,
			"type": "<ALPHANUM>",
			"position": 4
		}
	]
}

</br>通过html_strip被当作html标签过滤掉;Big通过lowercase filters转成了小写;The is a通过stop filters过滤掉了。


其它分词器

可以通过安装插件的方式添加社区或网上现有针对某些特定场景设计的分词器

安装插件命令:elasticsearch-plugin install {分词器}
安装后重启ES实例生效,可以通过api查看安装的插件 GET /_cat/plugins。

一些中文分词器:
1、更好的支持亚洲语言的的分词器:icu_analyzer

.bin/elasticsearch-plugin install analysis-icu

测试:

GET http://127.0.0.1:9200/_analyze
{
    "analyzer":"icu_analyzer",
    "text": "今天又熬夜了"
}

结果如下:

{
	"tokens": [
		{
			"token": "今天",
			"start_offset": 0,
			"end_offset": 2,
			"type": "<IDEOGRAPHIC>",
			"position": 0
		},
		{
			"token": "又",
			"start_offset": 2,
			"end_offset": 3,
			"type": "<IDEOGRAPHIC>",
			"position": 1
		},
		{
			"token": "熬夜",
			"start_offset": 3,
			"end_offset": 5,
			"type": "<IDEOGRAPHIC>",
			"position": 2
		},
		{
			"token": "了",
			"start_offset": 5,
			"end_offset": 6,
			"type": "<IDEOGRAPHIC>",
			"position": 3
		}
	]
}

默认情况下,每个汉字都会拆分成一个Token

2、IK分词器
支持自定义词库、热更新分词词典,github地址传送门>

3、THULAC(THU Lexucal Analyzer for Chinese)
清华大学出的一套中文分词器,插件 github地址传送门>


总结

分词器在全文搜索中起到了关键的作用,它将一个text文本经过分词器按特定规则分成一系列的Token,也是用来生成倒排索引的词项。

分词器进行文本处理共包括3个步骤,Character filters、Tokenizer、Token filters。

ES内部也已经内建了常见的分词器,可以基于内建的分词器进行扩展或者自定义特定的分词器以满足特定的场景。

参考:ES官方文档地址传送门>、analysis api地址传送门>