全文检索与常规关系型数据库SQL查询的显著区别,就是全文检索具备对大段文本进行分析的能力,它可以通过文本分析把大段的文本切分为细粒度的分词。
elasticsearch在两种情况下会用到文本分析:
- 原始数据写入索引时,如果索引的某个字段类型是text,则会将分析之后的内容写入索引
- 对text类型字段的索引数据做全文检索时,搜索内容也会经过文本分析。
1 文本分析的原理
elasticsearch规定,一个完整的文本分析过程需要经过>=0个字符过滤器,一个分词器,>=0分词过滤器的处理过程。
文本分析的顺序是先进行字符过滤器的处理,然后是分词器的处理,最后分词过滤器的处理。
字符过滤器
用于对原始文本做简单字符过滤和转换,例如,elasticsearch内置的HTML strip字符过滤器可以方便地删除文本中的HTML标签。
分词器
把原始的文本按照一定的规则切分成一个个单词,对于中文文本而言分词的效果和中文分词器的类型有关。
分词器还会保留每个关键词在原始文本中出现的位置数据。
elasticsearch的内置的分词器有几十种,通常针对不同语言的文本需要使用不同的分词器。
分词过滤器
用于对分词器切词后的单词做进一步的过滤和转换,例如:停用词分词过滤器(stop token filter)可以把分词器切分出来的冠词a,介词of等无实际意义的单词直接丢弃,避免影响搜索结果。
2 内置的分析器分析文本
elasticsearch内置了8种分析器,可以直接使用
标准分析器
是文本分析默认的分析器,标准分析器的内部包含一个标准分词器(standard tokenizer)和一个小写分词过滤器(lower case token ilter),可以使用_analyze端点测试分析器默认的分词效果,如:
POST _analyze
{
"analyzer":"standard",
"text":"The 2025头条新闻has spread out。"
}
{
"tokens" : [
{
"token" : "the",
"start_offset" : 0,
"end_offset" : 3,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "2025",
"start_offset" : 4,
"end_offset" : 8,
"type" : "<NUM>",
"position" : 1
},
{
"token" : "头",
"start_offset" : 8,
"end_offset" : 9,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "条",
"start_offset" : 9,
"end_offset" : 10,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "新",
"start_offset" : 10,
"end_offset" : 11,
"type" : "<IDEOGRAPHIC>",
"position" : 4
},
{
"token" : "闻",
"start_offset" : 11,
"end_offset" : 12,
"type" : "<IDEOGRAPHIC>",
"position" : 5
},
{
"token" : "has",
"start_offset" : 12,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 6
},
{
"token" : "spread",
"start_offset" : 16,
"end_offset" : 22,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "out",
"start_offset" : 23,
"end_offset" : 26,
"type" : "<ALPHANUM>",
"position" : 8
}
]
}
可以看到,标准分析器默认的切词效果可以实现将英语的大写转为小写字母,这是因为该分析器内置了一个将大写字母转为小写字母的小写分词过滤器;
另外,按照空格来切分英语单词,数字作为一个整体不会被切分,中文文本会被切分成一个个汉字,标点符合则是被自动去除。
标准分析器可以配置停用词分词过滤器去除没有实际意义的虚词,还可以通过设置切词粒度来配置分词的长度。
例如:把标准分析器设置到索引中。
PUT standard-text
{
"settings": {
"analysis": {
"analyzer": {
"my_standard_analyzer":{
"type":"standard",
"max_token_length":3,
"stopwords":"_english_"
}
}
}
},
"mappings": {
"properties": {
"content":{
"type": "text",
"analyzer": "my_standard_analyzer"
}
}
}
}
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "standard-text"
}
在上面的索引standard-text中,配置了一个名为my_standard_analyzer的分析器。type为standard表明它使用了内置的标准分析器;
max_token_length=3,表示切词的最大长度设置为3,这样数字和英文长度都会被切分为不超过3个字符的形式;
stopwords为_english_表示该分析器会自动过滤英文的停用词。
该索引只有一个文本字段content,映射中指定了该字段的分析器为my_standard_analyzer,表示建索引时该字段的文本数据使用my_standard_analyzer做文本分析。接下来看看新的切词效果:
POST standard-text/_analyze
{
"analyzer":"my_standard_analyzer",
"text":"The 2025头条新闻has spread out。"
}
{
"tokens" : [
{
"token" : "202",
"start_offset" : 4,
"end_offset" : 7,
"type" : "<NUM>",
"position" : 1
},
{
"token" : "5",
"start_offset" : 7,
"end_offset" : 8,
"type" : "<NUM>",
"position" : 2
},
{
"token" : "头",
"start_offset" : 8,
"end_offset" : 9,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "条",
"start_offset" : 9,
"end_offset" : 10,
"type" : "<IDEOGRAPHIC>",
"position" : 4
},
{
"token" : "新",
"start_offset" : 10,
"end_offset" : 11,
"type" : "<IDEOGRAPHIC>",
"position" : 5
},
{
"token" : "闻",
"start_offset" : 11,
"end_offset" : 12,
"type" : "<IDEOGRAPHIC>",
"position" : 6
},
{
"token" : "has",
"start_offset" : 12,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "spr",
"start_offset" : 16,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 8
},
{
"token" : "ead",
"start_offset" : 19,
"end_offset" : 22,
"type" : "<ALPHANUM>",
"position" : 9
},
{
"token" : "out",
"start_offset" : 23,
"end_offset" : 26,
"type" : "<ALPHANUM>",
"position" : 10
}
]
}
可以明显看出该结果跟使用默认的标准分析器standard的有两点不同,一是分词the作为停用词被过滤掉了,二是每个分词的长度都不超过3.
简单分析器
简单分析器(simple analyzer)只由一个小写分词器(lowercase tokenizer)组成,它的功能是将文本在任意非字母字符(如数字,空格,连字符)处进行切分,丢弃非字母字符,并将大写字母改为小写字母.
例如:
POST _analyze
{
"analyzer": "simple",
"text":"The 2025头条新闻 hasn`t spread out。"
}
{
"tokens" : [
{
"token" : "the",
"start_offset" : 0,
"end_offset" : 3,
"type" : "word",
"position" : 0
},
{
"token" : "头条新闻",
"start_offset" : 8,
"end_offset" : 12,
"type" : "word",
"position" : 1
},
{
"token" : "hasn",
"start_offset" : 13,
"end_offset" : 17,
"type" : "word",
"position" : 2
},
{
"token" : "t",
"start_offset" : 18,
"end_offset" : 19,
"type" : "word",
"position" : 3
},
{
"token" : "spread",
"start_offset" : 20,
"end_offset" : 26,
"type" : "word",
"position" : 4
},
{
"token" : "out",
"start_offset" : 27,
"end_offset" : 30,
"type" : "word",
"position" : 5
}
]
}
可以看出,简单分析器丢弃了所有的非字母字符并从非字母字符处切词,大写会转为小写.
空格分析器
空格分析器(whitespace analyzer)的结构也非常简单,只由一个空格分词器(whitespace tokenizer)构成,它会在所有出现空格的地方切词,而每个分词本身的内容不变。
例如:
POST _analyze
{
"analyzer": "whitespace",
"text":"The 2025头条新闻 hasn`t spread out。"
}
{
"tokens" : [
{
"token" : "The",
"start_offset" : 0,
"end_offset" : 3,
"type" : "word",
"position" : 0
},
{
"token" : "2025头条新闻",
"start_offset" : 4,
"end_offset" : 12,
"type" : "word",
"position" : 1
},
{
"token" : "hasn`t",
"start_offset" : 13,
"end_offset" : 19,
"type" : "word",
"position" : 2
},
{
"token" : "spread",
"start_offset" : 20,
"end_offset" : 26,
"type" : "word",
"position" : 3
},
{
"token" : "out。",
"start_offset" : 27,
"end_offset" : 31,
"type" : "word",
"position" : 4
}
]
}
3 使用IK分词器分析文本
由于中文字符是方块字,默认的标准分词器把中文文本切成孤立的汉字不能正确的反映中文文本的语义。IK分词器是比较受欢迎的中文分词器。
安装ik分词器
1.进入IK分词器的GitHub页面,下载与elasticsearch配套的分词器安装包,也要选择对应的版本。
2.进入elasticsearch的安装目录,找到plugins文件夹,在里面新建一个名为ik的文件夹,把下载的安装包解压后放进ik文件夹中。
3.重启elasticsearch服务.
使用ik分词器
IK分词器提供了两种分析器供开发人员使用:ik_smart 和 ik_max_word
POST _analyze
{
"analyzer": "ik_smart",
"text":"诺贝尔奖是著名的世界级别大奖"
}
{
"tokens" : [
{
"token" : "诺贝尔奖",
"start_offset" : 0,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "是",
"start_offset" : 4,
"end_offset" : 5,
"type" : "CN_CHAR",
"position" : 1
},
{
"token" : "著名",
"start_offset" : 5,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "的",
"start_offset" : 7,
"end_offset" : 8,
"type" : "CN_CHAR",
"position" : 3
},
{
"token" : "世界",
"start_offset" : 8,
"end_offset" : 10,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "级别",
"start_offset" : 10,
"end_offset" : 12,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "大奖",
"start_offset" : 12,
"end_offset" : 14,
"type" : "CN_WORD",
"position" : 6
}
]
}
可以看出,ik_smart将文本切分成了7个分词.
如果使用ik_max_word,则会切分成更多的分词。
POST _analyze
{
"analyzer": "ik_max_word",
"text":"诺贝尔奖是著名的世界级别大奖"
}
{
"tokens" : [
{
"token" : "诺贝尔奖",
"start_offset" : 0,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "诺贝尔",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "贝尔",
"start_offset" : 1,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "奖",
"start_offset" : 3,
"end_offset" : 4,
"type" : "CN_CHAR",
"position" : 3
},
{
"token" : "是",
"start_offset" : 4,
"end_offset" : 5,
"type" : "CN_CHAR",
"position" : 4
},
{
"token" : "著名",
"start_offset" : 5,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "的",
"start_offset" : 7,
"end_offset" : 8,
"type" : "CN_CHAR",
"position" : 6
},
{
"token" : "世界级",
"start_offset" : 8,
"end_offset" : 11,
"type" : "CN_WORD",
"position" : 7
},
{
"token" : "世界",
"start_offset" : 8,
"end_offset" : 10,
"type" : "CN_WORD",
"position" : 8
},
{
"token" : "级别",
"start_offset" : 10,
"end_offset" : 12,
"type" : "CN_WORD",
"position" : 9
},
{
"token" : "大奖",
"start_offset" : 12,
"end_offset" : 14,
"type" : "CN_WORD",
"position" : 10
}
]
}
从结果中看,切分了11个分词,这种模式下,切出来的分词会尽可能多,更便于被搜索到.
通常,索引时的文本分析使用ik_max_word更加合适,而全文检索的文本分析使用ik_smart较为多见。值得注意的是,每个分词结果用偏移量保存每个分词在原始文本中出现的位置,这类位置信息在全文检索结果高亮展示时会被用到。
下面的请求将新建一个索引ik-text并使用IK分词器进行文本分析.
PUT ik-text
{
"settings": {
"analysis": {
"analyzer": {
"default":{
"type":"ik_max_word"
},
"default_search":{
"type":"ik_smart"
}
}
}
},
"mappings": {
"properties": {
"content":{
"type": "text"
},
"abstract":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_max_word"
}
}
}
}
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "ik-text"
}
在索引ik-text中,指定text类型的字段默认是索引分析器是ik_max_word,而默认的全文检索分析器是ik_smart。content字段正式采用的这种分析方式,而abstract字段则是在映射中显示地指定了索引时的分析器为ik_smart,搜索时的分析器为ik_max_word,这会覆盖掉索引默认的分析器配置。
注意:IK分词器虽然能够识别很多中文词,但是无法自动发现新词,如果想扩展自己的词典,则需要把扩展的内容配置到IK分词器目录下的IK Analyzer.cfg.xml文件中。即便如此,由于IK分词器切词的粒度不够细,在面对姓名,数字,英文单词的检索时常常力不从心.
4 自定义文本分析器
字符过滤器
字符过滤器是文本分析的第一个环节,在开始分词之前,字符过滤器可以丢弃一些没有意义的字符。
1. HTML strip字符过滤器
用于去除文本中的HTML标签,还可以用于解析类似于&的转义字符串。
POST _analyze
{
"char_filter": ["html_strip"],
"tokenizer": {
"type":"keyword"
},
"filter": [],
"text": [
"Tom & Jerrey < <b>world</b>"
]
}
{
"tokens" : [
{
"token" : "Tom & Jerrey < world",
"start_offset" : 0,
"end_offset" : 34,
"type" : "word",
"position" : 0
}
]
}
可以看到,分析后的文本中,HTML标签被去掉了,转义字符串在输出结果中也转义成功。
2. 映射字符过滤器
映射字符过滤器会根据提供的字符映射,把文本中出现的字符转换为映射的另一种字符,例如:
POST _analyze
{
"char_filter": [
{
"type":"mapping",
"mappings":[
"& => and"
]
}
],
"tokenizer": {
"type":"keyword"
},
"filter": [],
"text": [
"Tom & Jerrey"
]
}
{
"tokens" : [
{
"token" : "Tom and Jerrey",
"start_offset" : 0,
"end_offset" : 12,
"type" : "word",
"position" : 0
}
]
}
此时,&已经转换为and.
3. 模式替换字符过滤器
根据指定的正则表达式把匹配的文本转换成指定的字符串。例如:
POST _analyze
{
"char_filter": [
{
"type":"pattern_replace",
"pattern":"runoo+b",
"replacement":"tomcat"
}
],
"tokenizer": {
"type": "keyword"
},
"filter": [],
"text":[
"runooobbbb"
]
}
{
"tokens" : [
{
"token" : "tomcatbbb",
"start_offset" : 0,
"end_offset" : 10,
"type" : "word",
"position" : 0
}
]
}
5. 分词器
分词器(tokenizer)是文本分析的灵魂,它是文本分析过程中不可缺少的一部分,因为它直接决定按照怎样的算法来切分。
分词器会把原始文本切分为一个个分词(token),通常分词器会保存每个分词的以下三种信息:
1.文本分析后每个分词的相对顺序,主要用于短语搜索和单词邻近搜索。
2.字符偏移量,记录分词在原始文本中出现的位置。
3.分词类型,记录分词的种类,例如单词,数字等.
分词器按照切分方式大致分为3种类型:
1.面向单词的分词器会把文本切分成独立的单词。
2.部分单词分词器会把每个单词按照某种规则切分成小的字符串碎片。
3.特定结构的分词器主要针对特定格式的文本,比如:邮箱,邮政编码,路径等
1.面向单词的分词器
elasticsearch 7.9.1内置的面向单词的分词器,如标准分词器,小写分词器,空格分词器等。
2.部分单词分词器
这种分词器会把完整的单词按照某种规则切分成一些字符串碎片,搜索时其可用于部分匹配,这种分词器主要有两种:
- N元语法分词器
- 侧边N元语法分词器
对于N元语法分词器,效果如下:
POST _analyze
{
"char_filter": [],
"tokenizer": {
"type":"ngram",
"min_gram":2,
"max_gram":3,
"token_chars":[
"letter"
]
},
"filter": [],
"text": [
"tom cat8"
]
}
{
"tokens" : [
{
"token" : "to",
"start_offset" : 0,
"end_offset" : 2,
"type" : "word",
"position" : 0
},
{
"token" : "tom",
"start_offset" : 0,
"end_offset" : 3,
"type" : "word",
"position" : 1
},
{
"token" : "om",
"start_offset" : 1,
"end_offset" : 3,
"type" : "word",
"position" : 2
},
{
"token" : "ca",
"start_offset" : 4,
"end_offset" : 6,
"type" : "word",
"position" : 3
},
{
"token" : "cat",
"start_offset" : 4,
"end_offset" : 7,
"type" : "word",
"position" : 4
},
{
"token" : "at",
"start_offset" : 5,
"end_offset" : 7,
"type" : "word",
"position" : 5
}
]
}
在这个ngram分词器的配置中,设置了每个分词的最小长度为2,最大长度为3,表示ngram分词器会通过长度为2和3的滑动窗口切分文本,得到的字串作为分词。
token_chars配置了只保留字母作为分词,其余的字符(例如空格和数字)则会被过滤掉。
对于侧边N元语法分词器与N元语法分词器的区别在于,其在分词时总是从第一个字母开始,会保留一定长度的前缀字符,例如:
POST _analyze
{
"char_filter": [],
"tokenizer": {
"type":"edge_ngram",
"min_gram":2,
"max_gram":5
},
"filter": [],
"text": [
"tom cat8"
]
}
{
"tokens" : [
{
"token" : "to",
"start_offset" : 0,
"end_offset" : 2,
"type" : "word",
"position" : 0
},
{
"token" : "tom",
"start_offset" : 0,
"end_offset" : 3,
"type" : "word",
"position" : 1
},
{
"token" : "tom ",
"start_offset" : 0,
"end_offset" : 4,
"type" : "word",
"position" : 2
},
{
"token" : "tom c",
"start_offset" : 0,
"end_offset" : 5,
"type" : "word",
"position" : 3
}
]
}
这里设置了每个分词的长度最小为2,最大为5,这种分词器很适合做前缀搜索。
3.特定结构的分词器
如下分词器可以对正则表达式,分隔符或者路径等进行切分。