理解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的处理过程如下:
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有以下特点:
- 没有配置char_filter,因此不会去除html标签等。char filter阶段不做任何的事情。见下面的例子。
- 使用standard tokenizer来切词,过滤掉标点符号,按照空格切词(中文会按照字进行切分),但是 he's 这种会当做一个token处理
- 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 建立很好的理解,帮助我们调试属于自己的分析器
- 调试analyzer, 指定analyzer即可
POST _analyze
{
"text": "<html> hello world !!! He's </html>",
"analyzer": "standard"
}
- 如此类推,如果要调试 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有以下特点:
- char_filter,使用了自定义的char_filter:
- my_html_strip: 过滤html标签
- my_punctuation_mapping: 对特定的标点符号进行转换, 转换规则为: * => _ ,= => ~
- tokenizer: 使用空格进行切词(whitespace)
- 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
}
]
}