一、简介
分词器在es中是一个不可不提的东西,实际上es所谓的全文检索能力正是基于其分词器的能力提供的。
当数据从外部进入es的时候,分词字段(类型为text的字段)会经过分词器的处理,进过分词的内容会根据分词器的逻辑被拆分为一个一个的token。
经过一系列的排布进行存储。
当我们在检索的时候,我们会输入我们的搜索词,同样的,我们的搜索词也会被分词器以同样的逻辑进行处理。然后去和存储的时候的token进行匹配。
所以对于输入文本以及存储文本的分词就显得很重要,分词器的效果和粒度决定了你检索时候的效果表现。
所以我们这里来介绍一下关于分词器的内容。当然你的索引中如果不包含text类型的字段,那你其实不用关心这个问题。
所以其实根据这段描述我们其实知道了分词器工作的位置,就是发生在数据写入的时候,会经过分词器做结果存储。
其次就是会在检索的时候,对检索词做分词器处理,然后得到的结果在去查询匹配。
众所周知的是,es的文档是非常完备的。分词器文档地址
二、分词器的组成
1、规范化(normalization)
es中的分词器由三个部分组成,字符过滤器(character filter),分词器(tokenizer),token过滤器(token filter)。
这三个部分共同作用,完成了文本的规范化(normalization)
这几个部分其中分词器是最重要的,下面我们就挨个来解释一下这几个部分的作用和逻辑,方便我们后续进行使用。
那么为什么需要规范化呢,我们来看一下
当我在ES中存储了一句话,“I am a student and I Love China”.
此时用户再来做检索,用户输入了一个i love china,这时候我们明显看到用户的输入和我们存入的东西有差异。
1、大小写不一致,我们存进去的是大写的I Love China,但是用户输入的却是小写。
2、我们存储的是一句完整的,用户只是搜索其中一段。
基于这些问题,用户查的和我们存的不一致,此时我们需要对数据做规范化,从而让用户的动作和存储的动作保持一致。在ES中的规范化包含以下几个内容。
- 切词(word segmentation):把输入以及存入的文本按照规则切分然后存储和匹配。
- 规范化(normalization):对切分的词汇做规范化,包括大写转小写,复数转单数,去除介词和停用词等。
- 去重(distinct):对于切分后重复的词汇进行去重处理。
- 字典序(sorted):对于切分后的词汇进行字典序排序进行存储。
有了规范化这个操作,我们就来看一下,我们在存储入ES的时候,我们存储了一句文本
You are not a Nobody, You are My friends.
我们不要去考虑有没有语法问题,可能就是个英语菜鸡(比如我)存进去的。
此时ES在存储的时候,会把这句话进行规范化,包括切词,规范化,去重,字典序。此时这句话被处理为。
friend my not nobody somebody you
你能看到,他的介词和一些副词这种对于检索没意义的词就被去掉了,并且他的大写都被转小写了,并且相同的词汇被移除了,并且复数也被转为了单数,而且按照字典序进行了排序。这样处理之后,用户再搜you are my friend的时候,
就可以命中了,因为用户输入的词汇也会被规范化,用户输入的内容变成了friend you my这样。此时就是可以匹配了。
否则如果双方规则不一致,最后就会导致,明明我要搜的就是那个意思,但是你因为一些时态,或者单复数或者大小写最终导致不能命中。
所以我们可以看到,规范化发生的时机就是分词阶段,不管是存储的分词还是搜索词的分词 都会进行相同规则的规范化,此时就能保持一致的语义然后进行匹配。这就是规范化的作用。
但是我们说规范化是一种思想,他的作用就是存储和检索保持一致的语义,避免因为一些无关的因素影响检索命中。
这种思想下每个分词器都有自己的实现,ES中提供了一种查看分词的语法 _analyze 可以观察到分词的结果。我们来看一下如何使用。
# 我们使用标准分词器来查看他的分词结果
GET _analyze
{
"text": ["You are not a Nobody, You are My friends."],
"analyzer": "standard"
}
结果如下:
{
"tokens": [
{
"token": "you",
... 省略和规范化无关的
},
{
"token": "are",
},
{
"token": "not",
},
{
"token": "a",
},
{
"token": "nobody",
},
{
"token": "you",
},
{
"token": "are",
},
{
"token": "my",
},
{
"token": "friends",
}
]
}
我们看到标准分词器没有帮我们做复数转单数这种,以及一些冠词也没去掉。这就是他的实现,再说一次每种分词器的实现并不相同。
而不管怎么实现,无论是内置的还是自定义的 — 都只是一个包含三个较低级别构建块的包:字符过滤器、 tokenizers 和 token 过滤器。
内置分析器将这些构建块预先打包到适用于不同语言和文本类型的分析器中。Elasticsearch 还公开了各个构建块,以便可以将它们组合起来定义新的自定义分析器。内置的你可以直接使用。
2、字符过滤器(character filter)
字符过滤器用于在将字符流传递给分词器之前对其进行预处理。所以我们可以看到他的作用时机位于传递给分词器之前的一步。
字符筛选器将原始文本作为字符流接收,并可以通过添加、删除或更改字符来转换流。例如,字符过滤器可用于将印度教-阿拉伯数字 (٠١٢٣٤٥٦٧٨٩) 转换为其阿拉伯语-拉丁语等价物 (0123456789),或从流中去除 等 HTML 元素。
而且,分析器可以有零个或多个字符筛选器,这些筛选器是按顺序应用的。
我说白了,他的作用是对原始文本进行一些字符的去除,比如我们源端的文本有一些html标签或者是其他标签我们要在存储的时候进行去除。
es中内置了几种字符过滤器,你可以用这几种来进行组合,实现你自定义的字符过滤器。而且他可以同时配置多个,他是按照配置顺序来生效的。
下面我们来一一看一下这几种内置处理器。
2.1、HTML Strip
2.1.1、基本使用
The html_strip character filter strips out HTML elements like and decodes HTML entities like &.
html_strip字符过滤器去除 HTML 元素(如 )并解码 HTML 实体(如 &)。对应的lucene的类位于HTMLStripCharFilter 。
我们来使用api先测试一下,以下 analyze API 请求使用 html_strip筛选器将文本
I'm so happy!
更改为 \n我太高兴了!GET /_analyze
{
"tokenizer": "keyword",
"char_filter": [
"html_strip"
],
"text": "<p>I'm so <b>happy</b>!</p>"
}
先不要关注tokenizer这个关键词,我们这句代码的含义就是我们对文本内容为
"
I’m so happy!
"的文本,使用html_strip这个字符过滤器进行处理,我们来看看那处理结果是啥。{
"tokens": [
{
"token": """
I'm so happy!
""",
......
}
]
}
我们看到他把html标签成功移除了,符合我们的预期。
这个案例比较简陋,就是用_analyze看了一下,实际上我们开发的时候,在你正式创建索引之前一般都是先看下是不是符合你的要求。下面我们在创建索引中实际使用一下。我们来创建索引 API 请求使用 html_strip过滤器配置新的 自定义分析器。
PUT /my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"html_strip"
]
}
}
}
}
}
不要管这个语法,我们在后面自定义分词器的时候会详细来分析。这就是创建了一个索引,配置了一个分词器,分词器我们说是由三个部分组成的(字符过滤器、 tokenizers 和 token 过滤器),我们这里就是指定了两个,tokenizers是keyword,char_filter用的内置的html_strip。而这个分词器我们命名为my_analyzer我们来看下结果。
GET my-index-000001/_analyze
{
"analyzer": "my_analyzer",
"text": "<p>I'm so <b>happy</b>!</p>"
}
结果如下:
{
"tokens": [
{
"token": """
I'm so happy!
""",
......
}
]
}
符合预期。
2.1.2、可配参数
(可选,字符串数组)不带尖括号的 HTML 元素数组 (< >)。从文本中去除 HTML 时,过滤器会跳过这些 HTML 元素。例如,值 [ “p” ] 会跳过
HTML 元素。
这个过滤器在使用的时候是可以配置一个参数的,那就是保留词,有时候我们也不是都要过滤掉的,有些词汇还是要保留。这时候就要使用一个配置。escaped_tags ,这个配置可以添加你过滤之外要保留的词汇。默认这个为空,也就是所有的html标签都被过滤。
假如此时我要保留我文本中的p标签,我要留着分段展示,其他的html都过滤。
我们可以这么做。我们来在索引中自定义一个分词器,分词器中的字符过滤器我们自己来定义。
这个dsl中我们自己定义了一个char_filter叫做my_custom_html_strip_char_filter,我们的分词器名字叫做my_analyzer。
PUT my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"my_custom_html_strip_char_filter"
]
}
},
"char_filter": {
"my_custom_html_strip_char_filter": {
"type": "html_strip",
"escaped_tags": [
"p"
]
}
}
}
}