今天分析相关数据的检索和数据分析:
为了方便我们学习,我们导入
kibana
为我们提供的范例数据。
目前为止,我们已经探索了如何将数据放入
Elasticsearch
, 现在来讨论下如 何将数据从 Elasticsearch
中拿出来,那就是通过搜索。毕竟,如果不能搜索数据, 那么将其放入搜索引擎的意义又何在呢?
幸运的是,
Elasticsearch
提供了丰富的 接口来搜索数据,涵盖了 Lucene
所有的搜索功能。因为
Elasticsearch
允许构建 搜索请求的格式很灵活,请求的构建有无限的可能性。要了解哪些查询和过滤器 的组合适用于你的数据,
最佳的方式就是进行实验,因此不要害怕在项目的数据 上尝试这些组合,这样才能弄清哪些更适合你的需求。
_search
接口
所有的
REST
搜索请求使用
_search
接口,既可以是
GET
请求,也可以是
POST 请求,也可以通过在搜索 URL
中指定索引来限制范围。 _search 接口有两种请求方法,一种是基于
URI
的请求方式,另一种是基于 请求体的方式,无论哪种,他们执行的语法都是基于 DSL
(
ES
为我们定义的查询 语言,基于 JSON
的查询语言),只是形式上不同。我们会基于请求体的方式来 学习。比如说:
get kibana_sample_data_flights/_search
{
"query": {
"match_all": {
}
}
}
"query": {
"match_all": {
}
}
}
或
get kibana_sample_data_flights/_search
{
"query": {
"match_none": {
}
}
}
"query": {
"match_none": {
}
}
}
当然上面的查询没什么太多的用处,因为他们分别代表匹配所有和全不匹配。 所以我们经常要使用各种语法来进行查询,一旦选择了要搜索的索引,就需 要配置搜索请求中最为重要的模块。这些模块涉及文档返回的数量,选择最佳的 文档返回,以及配置不希望哪些文档出现在结果中等等。
■
query-
这是搜索请求中最重要的组成部分,它配置了基于评分返回的最 佳文档,也包括了你不希望返回哪些文档。
■
size-
代表了返回文档的数量。
■
from-
和
size
一起使用,
from
用于分页操作。需要注意的是,为了确定 第 2
页的
10
项结果,
Elasticsearch
必须要计算前
20
个结果。如果结果集合不断 增加,获取某些靠后的翻页将会成为代价高昂的操作。■
_source
指定
_ source
字段如何返回。默认是返回完整的
_ source
字段。 通过配置_ source,
将过滤返回的字段。如果索引的文档很大,而且无须结果中的 全部内容,就使用这个功能。请注意,如果想使用它,就不能在索引映射中关闭 _ source 字段。
■
sort
默认的排序是基于文档的得分。如果并不关心得分,或者期望许多 文档的得分相同,添加额外的 sort
将帮助你控制哪些文档被返回。
结果起始和页面大小
命名适宜的
from
和
size
字段,用于指定结果的开始点,以及每“页
"
结果的 数量。举个例子,如果发送的 from
值是
7
,
size
值是
5,
那么
Elasticsearch
将返回 第 8
、
9
、
10
、
11
和
12
项结果
(
由于
from
参数是从
0
开始,指定
7
就是从第
8 项结果开始)
。如果没有发送这两个参数,
Elasticsearch
默认从第一项结果开始 ( 第
0
项结果
)
,在回复中返回
10
项结果。
例如
get kibana_sample_data_flights/_search
{
"from": 100,
"size": 20,
"query": {
"term": {
"DestCountry": "CN"
}
}
}
"from": 100,
"size": 20,
"query": {
"term": {
"DestCountry": "CN"
}
}
}
但是注意,
from
与
size
的和不能超过
index. max_result_window
这个索引配 置项设置的值。默认情况下这个配置项的值为 10000,
所以如果要查询
10000
条以 后的文档,就必须要增加这个配置值。例如,要检索第 10000
条开始的
200
条数 据,这个参数的值必须要大于 10200
,否则将会抛出类似“
Result window is too large'的异常。 由此可见,Elasticsearch
在使用
from
和
size
处理分页问题时会将所有数据 全部取出来,然后再截取用户指定范围的数据返回。所以在查询非常靠后的数据 时,即使使用了 from
和
size
定义的分页机制依然有内存溢出的可能,而
max_ result_ window 设置的
10000
条则是对
Elastiesearch
的一
.
种保护机制。 那么 Elasticsearch
为什么要这么设计呢
?
首先,在互联网时代的数据检索应 该通过相似度算法,提高检索结果与用户期望的附和度,而不应该让用户在检索 结果中自己挑选满意的数据。以互联网搜索为例,用户在浏览搜索结果时很少会 看到第 3
页以后的内容。假如用户在翻到第
10000
条数据时还没有找到需要的结 果,那么他对这个搜索引擎一定会非常失望。
_source
参数
元字段
_source
中存储了文档的原始数据。如果请求中没有指定
_source
, Elasticsearch 默认返回整个
_ source
, 或者如果
_ source
没有存储,那么就只返 回匹配文档的元数据:_ id
、
_type
、
_index
和
_score
。
例如:
get kibana_sample_data_flights/_search
{
"query": {
"match_all": {
}
},
"_source": [
"OriginCountry",
"DestCountry"
]
}
"query": {
"match_all": {
}
},
"_source": [
"OriginCountry",
"DestCountry"
]
}
你不仅可以返回字段列表
,
还可以指定通配符。例如
,
如果想同时返回
" DestCountry "和
" DestWeather "
字段,可以这样配置
_ source: "Dest*"
。 也可以使 用通配字符串的数组来指定多个通配符,例如_ source:[" Origin*", "* Weather "]
。
get kibana_sample_data_flights/_search
{
"query": {
"match_all": {
}
},
"_source": [
"Origin*",
"*Weather"
]
}
"query": {
"match_all": {
}
},
"_source": [
"Origin*",
"*Weather"
]
}
不仅可以指定哪些字段需要返回,还可以指定哪些字段无须返回。比如:
get kibana_sample_data_flights/_search
{
"_source": {
"includes": [
"*.lon",
"*.lat"
],
"excludes": "DestLocation.*"
}
}
"_source": {
"includes": [
"*.lon",
"*.lat"
],
"excludes": "DestLocation.*"
}
}
排序
大多搜索最后涉及的元素都是结果的排序
( sort )
。如果没有指定
sort
排序选 项,Elasticsearch
返回匹配的文档的时候,按照
_ score
取值的降序来排列,这样 最为相关的(
得分最高的
)
文档就会排名在前。为了对字段进行升序或降序排列,
指定映射的数组,而不是字段的数组。通过在
sort
中指定字段列表或者是字段映 射,
可以在任意数量的字段上进行排序。
例如:
get kibana_sample_data_flights/_search
{
"from": 100,
"size": 20,
"query": {
"match_all": {
}
},
"_source": [
"Origin*",
"*Weather"
],
"sort": [
{
"DistanceKilometers": "asc"
},
{
"FlightNum": "desc"
}
]
}
"from": 100,
"size": 20,
"query": {
"match_all": {
}
},
"_source": [
"Origin*",
"*Weather"
],
"sort": [
{
"DistanceKilometers": "asc"
},
{
"FlightNum": "desc"
}
]
}
检索
目前为止所进行的几乎所有搜索请求虽然有些条件限制,但是限制条件主要 是在规定 ES
返回的查询结果中哪些字段返回,哪些字段不返回,对于哪些文档 返回,哪些文档不返回,其实没有多少约束。真正如何更精确的找到我们需要的 文档呢?这就需要我们需要使用带条件的搜索,主要包括两类,基于词项的搜索 和基于全文的搜索。
基于词项的搜索
term
查询
对词项做精确匹配,数值、日期等等,如:
get kibana_sample_data_flights/_search
{
"query": {
"term": {
"dayOfWeek": 3
}
}
}
"query": {
"term": {
"dayOfWeek": 3
}
}
}
对于字符串而言,字符串的精确匹配是指字符的大小写,字符的数量和位置 都是相同的,词条(term
)查询使用字符的完全匹配方式进行文本搜索,词条查
询不会分析(
analyze
)查询字符串,给定的字段必须完全匹配词条查询中指定的 字符串。比如:
get kibana_sample_data_flights/_search
{
"query": {
"term": {
"OriginCityName": "Frankfurt am Main"
}
}
}
"query": {
"term": {
"OriginCityName": "Frankfurt am Main"
}
}
}
但是如果我们执行
get kibana_sample_data_flights/_search
{
"query": {
"term": {
"OriginCityName": "Frankfurt"
}
}
}
"query": {
"term": {
"OriginCityName": "Frankfurt"
}
}
}
结果却是
因此可以把
term
查询理解为
SQL
语句中
where
条件的等于号。
terms
查询
可以把
terms
查询理解为
SQL
语句中
where
条件的
in
操作符:
get kibana_sample_data_flights/_search
{
"query": {
"terms": {
"OriginCityName": [
"Frankfurt am Main",
"Cape Town"
]
}
}
}
"query": {
"terms": {
"OriginCityName": [
"Frankfurt am Main",
"Cape Town"
]
}
}
}
Elasticsearch
在
terms
查询中还支持跨索引查询,这类似于关系型数据库中 的一对多或多对多关系。比如,用户与文章之间就是一对多关系,可以在用户索 引中存储文章编号的数组以建立这种对应关系,而将文章的实际内容保存在文章 索引中(
当然也可以在文章中保存用户
ID)
。如果想将
ID
为
1
的用户发表的所有 文章都找出来,在文章索引中查询时为
POST /articles/ search
{
"query": {
"terms": {
"_id": {
"index": "users""id": 1,
"path": "articles"
}
}
}
}
"query": {
"terms": {
"_id": {
"index": "users""id": 1,
"path": "articles"
}
}
}
}
在上面的例子中,
terms
要匹配的字段是
id,
但匹配值则来自于另一个索引。 这里用到了 index
、
id
和
path
三个参数,它们分别代表要引用的索引、文档
ID 和字段路径。在上面的例子中,先会到 users
索引中在找
id
为
1
的文档,然后 取出 articles
字段的值与
articles
索引里的
_id
做对比,这样就将用户
1
的所有文 章都取出来了。
range
查询和
exists
查询
range
查询和过滤器的含义是不言而喻的,它们查询介于一定范围之内的值, 适用于数字、日期甚至是字符串。 为了使用范围查询,需要指定某个字段的上界和下界值。例如:
get kibana_sample_data_flights/_search
{
"query": {
"range": {
"FlightDelayMin": {
"gte": 100,
"lte": 200
}
}
}
}
"query": {
"range": {
"FlightDelayMin": {
"gte": 100,
"lte": 200
}
}
}
}
可以查询出延误时间在
100~200
之间的航班。其中:
gte
:大于等于 (
greater than and equal
)
gt
:大于 (
greater than
)
lte
:小于等于 (
less than and equal
)
lt
:大于 (
less than
)
boost
:相关性评分(后面的章节会讲到相关性评分)
exists
查询检索字段值不为空的的文档,无论其值是多少,在查询中通过
field 字段设置检查非空的字段名称,只能有一个。
prefix
查询
prefix
查询允许你根据给定的前缀来搜索词条,这里前缀在同样搜索之前是 没有经过分析的。例如:
get kibana_sample_data_flights/_search
{
"query": {
"prefix": {
"DestCountry": "C"
}
}
}
"query": {
"prefix": {
"DestCountry": "C"
}
}
}
找到航班目的国家中所有以
C
开头的文档。
wildcard
查询和
regexp
查询
wildcard
查询就是通配符查询。 使用字符串可以让 Elasticsearch
使用
*
通配符替代任何数量的字符
(
也可以 不含)
或者是使用
?
通配符替代单个字符。
例如,有
5
个单词:“
bacon
”“
barn
” “
ban
” 和“
baboon
” “
bam
”, “ba*n
”的查询会匹配“
bacon
”“
barn
” “
ban
” 和“
baboon
”
,
这是因
为
*
号可以匹配任何字符序列,而查询“
ba?n
” 只会匹配“
barn"
,因为
?
任何时 候都需要匹配一个单独字符。 也可以混合使用多个*
和
?
字符来匹配更为复杂的通配模板,比如
f*f?x
就可 以匹配 firefox
。
get kibana_sample_data_flights/_search
{
"query":{
"wildcard":{
"Dest":"*Marco*"
}
}
}
使用这种查询时,需要注意的是
wildcard
查询不像
match
等其他查询那样 轻量级。查询词条中越早出现通配符( *
或者
? ), Elasticsearch
就需要做更多的工作 来进行匹配。例如,对于查询词条“h*
”,
Elasticsearch
必须匹配所有以“
h
” 开 头的词条。如果词条是“hi*
”
,Elasticsearch
只需搜索所有“
hi"
开头的词条, 这是“h
” 开头的词条集合的子集,规模更小。考虑到额外开支和性能问题,在 实际生产环境中使用 wildcard
查询之前,需要先考虑清楚,并且尽量不要让通配 符出现在查询条件的第一位。 当然 Elasticsearch
也支持正则
regexp
查询,比如
get kibana_sample_data_flights/_search
{
"query": {
"regexp": {
"字段名": "正则表达式"
}
}
}
"query": {
"regexp": {
"字段名": "正则表达式"
}
}
}
文本分析
词条(
term
)查询和全文(
fulltext
)查询最大的不同之处是:全文查询首先 分析(Analyze
)查询字符串,使用默认的分析器分解成一系列的分词,
term1
, term2,
termN
,然后从索引中搜索是否有文档包含这些分词中的一个或多个。 所以,在基于全文的检索里,ElasticSearch
引擎会先分析(
analyze
)查询字 符串,将其拆分成小写的分词,只要已分析的字段中包含词条的任意一个,或全 部包含,就匹配查询条件,返回该文档;如果不包含任意一个分词,表示没有任 何文档匹配查询条件。 这里就牵涉到了 ES
里很重要的概念,文本分析,当然对应
非
text
类型字段 来说,本身不存在文本数据词项提取的问题,所以没有文本分析的问题。
什么分析
分析
( analysis )
是在文档被发送并加入倒排索引之前,
Elasticsearch
在其主体 上进行的操作。在文档被加入索引之前Elasticsearch
让每个被分析字段经过一 系列的处理步骤。
■字符过滤
--
使用字符过滤器转变字符。
■文本切分为分词
---
将文本切分为单个或多个分词。
■分词过滤
---
使用分词过滤器转变每个分词。
■分词索引
--
将这些分词存储到索引中。
比如有段话“
I like ELK
,
it include Elasticsearch&LogStash&Kibana
”,分析以 后的分词为 : i like elk it include elasticsearch logstash kibana
字符过滤
Elasticsearch
首先运行字符过滤器(
char filter
)。这些过滤器将特定的字符 序列转变为其他的字符序列。这个可以用于将 HTML
从文本中剥离,或者是将任 意数量的字符转化为其他字符(
也许是将“
I love u 2
”这种缩写的短消息纠正为“
I love you too”。 在“I like ELK……
”的例子里使用特定的过滤器将“
&
” 替换为“
and
”。
切分为分词
在应用了字符过滤器之后,文本需要被分割为可以操作的片段。底层的 Lucene 是不会对大块的字符串数据进行操作。相反,它处理的是被称为分词 ( token)的数据。
分词是从文本片段生成的,可能会产生任意数量
(
甚至是
0)
的分词。例如, 在英文中一个通用的分词是标准分词器,它根据空格、换行和破折号等其他字符, 将文本分割为分词。在我们的例子里,这种行为表现为将字符串“I like ELK
,
it include Elasticsearch&LogStash&Kibana”分解为分词
I like ELK it include Elasticsearch and LogStash Kibana。
分词过滤器
一旦文本块被转换为分词,
Elasticsearch
将会对每个分词运用分词过滤器 ( token filter)。 这些分词过滤器可以将一个分词作为输入, 然后根据需要进行 修改,添加或者是删除。最为有用的和常用的分词过滤器是小写分词过滤器,
它 将输人的分词变为小写,确保在搜索词条“nosql"
的时候,可以发现关于“
NoSq" 的聚会。分词可以经过多于 1
个的分词过滤器,每个过滤器对分词进行不同的操 作,将数据塑造为最佳的形式,便于之后的索引。 在上面的例子,
有
3
种分词过滤器
:
第
1
个将分词转为小写
,
第
2
个删除停用词 (停止词
)
“
and
”,第三个将词条“
tools"
作为“
technologies"
的同义词进行添 加。
分词索引
当分词经历了零个或者多个分词过滤器,它们将被发送到
Lucene
进行文档 的索引。这些分词组成了第 1
章所讨论的倒排索引。
分析器
所有这些不同的部分,组成了一个分析器
( analyzer ),
它可以定义为零个或多 个字符过滤器、1
个分词器、零个或多个分词过滤器。
Elasticsearch
中提供了很 多预定义的分析器。我们可以直接使用它们而无须构建自己的分析器。
配置分析器
_analyze
接口
GET /_analyze
POST /_analyze
GET /<index>/_analyze
POST /<index>/_analyze
可以使用
_analyze API
来测试
analyzer
如何解析我们的字符串的,在我们下 面的学习中,我们会经常用到这个接口来测试。
分词综述
因为文本分词会发生在两个地方:创建索引
:
当索引文档字符类型为
text
时, 在建立索引时将会对该字段进行分词;搜索:当对一个 text
类型的字段进行全文 检索时,会对用户输入的文本进行分词。 所以这两个地方都可以对分词进行配置。
创建索引时
ES
将按照下面顺序来确定使用哪个分词器:
1
、先判断字段是否有设置分词器,如果有,则使用字段属性上的分词器设 置;
2
、如果设置了
analysis.analyzer.default
,则使用该设置的分词器;
3
、如果上面两个都未设置,则使用默认的
standard
分词器。
设置索引默认分词器
PUT test
{
"settings": {
"analysis": {
"analyzer": {
"default": {
"type": "simple"
}
}
}
}
}
"settings": {
"analysis": {
"analyzer": {
"default": {
"type": "simple"
}
}
}
}
}
还可以为索引配置内置分词器,并修改内置的部分选项修改它的行为
:
put test
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "standard",
"stopwords": [
"the",
"a",
"an",
"this",
"is"
]
}
}
}
}
}
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "standard",
"stopwords": [
"the",
"a",
"an",
"this",
"is"
]
}
}
}
}
}
如何为字段指定内置分词器
put test
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "standard",
"search_analyzer": "simple"
}
}
}
}
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "standard",
"search_analyzer": "simple"
}
}
}
}
甚至还可以自定义分词器。 我们综合来看看分词的设置,并且通过_analyzer
接口来测试分词的效果:
PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"std_english": {
"type": "standard",
"stopwords": "_english_"
}
}
}
},
"mappings": {
"properties": {
"my_text": {
"type": "text",
"analyzer": "standard",
"fields": {
"english": {
"type": "text",
"analyzer": "std_english"
}
}
}
}
}
}
"settings": {
"analysis": {
"analyzer": {
"std_english": {
"type": "standard",
"stopwords": "_english_"
}
}
}
},
"mappings": {
"properties": {
"my_text": {
"type": "text",
"analyzer": "standard",
"fields": {
"english": {
"type": "text",
"analyzer": "std_english"
}
}
}
}
}
}
我们首先,在索引
my_index
中配置了一个分析器
std_english
,
std_english 中使用了内置分析器 standard
,并将
standard
的停止词模式改为英语模式 _english_(缺省是没有的),对字段
my_text
配置为多数据类型,分别使用了两 种分析器,standard
和
std_english
。
POST /my_index/_analyze
{
"field": "my_text",
"text": "The old brown cow"
}
POST /my_index/_analyze
{
"field": "my_text.english",
"text": "The old brown cow"
}
通过上述运行我们可以看到,分析器
std_english
中的
The
被删除,而
standard 中的并没有。这是因为 my_text.english
配置了单独的停止词。
文档搜索时
文档搜索时使用的分析器有一点复杂,它依次从如下参数中如果查找文档分 析器,如果都没有设置则使用 standard
分析器
:
1
、搜索时指定
analyzer
参数
2
、创建索引时指定字段的
search_analyzer
属性
3
、创建索引时字段指定的
analyzer
属性
4
、创建索引时
setting
里指定的
analysis.analyzer.default_search
5
、如果都没有设置则使用
standard
分析器
比如:
搜索时指定
analyzer
查询参数
GET my_index/_search
{
"query": {
"match": {
"message": {
"query": "Quick foxes",
"analyzer": "stop"
}
}
}
}
"query": {
"match": {
"message": {
"query": "Quick foxes",
"analyzer": "stop"
}
}
}
}
指定字段的
analyzer
和
seach_analyzer
PUT my_index
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "whitespace",
"search_analyzer": "simple"
}
}
}
}
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "whitespace",
"search_analyzer": "simple"
}
}
}
}
指定索引的默认搜索分词器
PUT my_index
{
"settings": {
"analysis": {
"analyzer": {
"default": {
"type": "simple"
},
"default_seach": {
"type": "whitespace"
}
}
}
}
}
"settings": {
"analysis": {
"analyzer": {
"default": {
"type": "simple"
},
"default_seach": {
"type": "whitespace"
}
}
}
}
}
内置分析器
前面说过,每个被分析字段经过一系列的处理步骤:
字符过滤
--
使用字符过滤器转变字符。 文本切分为分词---
将文本切分为单个或多个分词。 分词过滤---
使用分词过滤器转变每个分词。 每个分析器基本上都要包含上面三个步骤至少一个。其中字符过滤器可以为 0 个,也可以为多个,分词器则必须,但是也只能有一个,分词过滤器可以为
0 个,也可以为多个。Elasticsearch
已经为我们内置了很多的字符过滤器、分词器和分词过滤器,
以及分析器。不过常用的就是那么几个。
字符过滤器(
Character filters
)
字符过滤器种类不多。
elasticearch
只提供了三种字符过滤器: HTML 字符过滤器(
HTML Strip Char Filter
)
从文本中去除
HTML
元素。
POST _analyze
{
"tokenizer": "keyword",
"char_filter": ["html_strip"],
"text":"<p>I'm so <b>happy</b>!</p>"
}
映射字符过滤器(
Mapping Char Filter
)
接收键值的映射,每当遇到与键相同的字符串时,它就用该键关联的值替换 它们。
PUT pattern_test4
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"my_char_filter"
]
}
},
"char_filter": {
"my_char_filter": {
"type": "mapping",
"mappings": [
"James => 666",
"13 号 => 888"
]
}
}
}
}
}
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"my_char_filter"
]
}
},
"char_filter": {
"my_char_filter": {
"type": "mapping",
"mappings": [
"James => 666",
"13 号 => 888"
]
}
}
}
}
}
上例中,我们自定义了一个分析器,其内的分词器使用关键字分词器,字符 过滤器则是自定制的,将字符中的 James
替换为
666
,
13
号替换为
888
。
POST pattern_test4/_analyze
{
"analyzer": "my_analyzer",
"text": " James
热爱
13
号,可惜后来
13
号结婚了
"
}
模式替换过滤器(
Pattern Replace Char Filter
)
使用正则表达式匹配并替换字符串中的字符。但要小心你写的糟糕的正则表 达式。因为这可能导致性能变慢!
比如:
POST _analyze
{
"analyzer": "standard",
"text": "My credit card is 123-456-789"
}
这样分词,会导致
123-456-789
被分为
123 456 789
,但是我们希望 123-456-789 是一个整体,可以使用模式替换过滤器,替换掉“
-
”。
PUT pattern_test5
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "standard",
"char_filter": [
"my_char_filter"
]
}
},
"char_filter": {
"my_char_filter": {
"type": "pattern_replace",
"pattern": "(\\d+)-(?=\\d)",
"replacement": "$1_"
}
}
}
}
}
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "standard",
"char_filter": [
"my_char_filter"
]
}
},
"char_filter": {
"my_char_filter": {
"type": "pattern_replace",
"pattern": "(\\d+)-(?=\\d)",
"replacement": "$1_"
}
}
}
}
}
POST pattern_test5/_analyze
{
"analyzer": "my_analyzer",
"text": "My credit card is 123-456-789"
}
把数字中间的“
-
”替换为下划线“
_
”,这样的话可以让“
123-456-789
”作 为一个整体,而不至于被分成 123 456 789
。
分词器(
Tokenizer
)
1.
标准分词器
(standard)
标准分词器
( standard tokenizer)
是一个基于语法的分词器,对于大多数欧洲 语言来说是不错的。它还处理了 Unicode
文本的切分。它也移除了逗号和句号这 样的标点符号。 “I have, potatoes.”切分后的分词分别是
” I”
、
” have”
和
” potatoes”
。
2.
关键词分词器
(keyword)
关键词分词器
( keyword tokenizer )
是
- -
种简单的分词器,将整个文本作为单 个的分词,提供给分词过滤器。只想应用分词过滤器,而不做任何分词操作时, 它可能非常有用。 'Hi, there.' 唯一的分词是
Hi, there
。
3.
字母分词器
(letter)
字母分词器根据非字母的符号
,
将文本切分成分词。例如,对于句子 “Hi,there."
分词是
Hi
和
there,
因为逗号、空格和句号都不是字母
: 'Hi, there. '分词是
Hi
和
there
。
4.
小写分词器
(lowercase)
小写分词器
( lowercase tokenizer)
结合了常规的字母分词器和小写分词过滤 器(
如你所想,它将整个分词转化为小写
)
的行为。通过
1
个单独的分词器来实现 的主要原因是,2
次进行两项操作会获得更好的性能。 'Hi, there.'分词是
hi
和
there
。
5.
空白分词器
(whitespace)
空白分词器
( whitespace tokenizer )
通过空白来分隔不同的分词,空白包括空 格、制表符、换行等。请注意,这种分词器不会删除任何标点符号,所以文本“Hi, there." 的分词
. 'Hi,
there. '
分词是
Hi,
和
there.
。
6.
模式分词器
(pattern)
模式分词器
( patterm tokenizer)
允许指定一个任 意的模式,将文本切分为分 词。被指定的模式应该匹配间隔符号。例如,可以创建一个定制分析器,它在出 现文本“. -.
”的地方将分词断开。
7. UAX URL
电子邮件分词器
(uax_url_email)
在处理英语单词的时候,标准分词器是非常好的选择。但是,当下存在不少 以网站地址和电子邮件地址结束的文本。标准分析器可能在你未注意的地方对其 进行了切分。例如,有一个电子邮件地址的样本 john.smith@example.com,
用标
准分词器分析它,切分后
: 'john.smith@example.com' 分词是 john.smith
和
example.com
。 它同样将 URL
切分为不同的部分
:
'http://example. com?q=foo'
分词是
http
、
example.com
、
q
和
foo
。 UAX URL 电子邮件分词器
( UAX URL email tokenizer )
将电子邮件和
URL
都作 为单独的分词进行保留。
8.
路径层次分词器
(path_hierarchy)
路径层次分词器
( path hierarchy tokenizer )
允许以特定的方式索引文件系统 的路径,这样在搜索时,共享同样路径的文件将被作为结果返回。例如,假设有 一个文件名想要索引,看上去是 这样的(ustl0oal/var/log/elasticsearch.log
。路径层次分词器将其切分为
: ' /usr/local/var/1og/elasticsearch. log' 分词是/usr
、
/usr/local
、
/usr/local/var
、
/usr/local/var/ log
和
/usr/local/var/
log/elasticsearch.1og
。 这意味着,一个用户查询时,和上述文件共享同样路径层次(
名字也是如此
) 的文件也会被匹配上。查询“/usr/local/var/log/es.log"
时,它和 “/usr/local/var/log/elasticsearch.log"
拥有同样的分词,因此它也会被作为结果 返回。
分词过滤器(
Token filters
)
1.
标准分词过滤器(
standard
)
不要认为标准分词过滤器
( standard token filter )
进行了什么复杂的计算,实 际上它什么事情也没做。
2.
小写分词过滤器(
lowercase
)
小写分词过滤器
( lowercase token filter)
只是做了这件事
:
将任何经过的分词 转换为小写。这应该非常简单也易于理解。
3.
长度分词过滤器(
length
)
长度分词过滤器
(length token filter)
将长度超出最短和最长限制范围的单词 过滤掉。举个例子,如果将 min
设置为
2
,并将
max
设置为
8
,任何小于
2
个字 符和任何大于 8
个字符的分词将会被移除。
4.
停用词分词过滤器(
stop
)
停用词分词过滤器
(stop token fite)
将停用词从分词流中移除。对于英文而言, 这意味着停用词列表中的所有分词都将会被完全移除。用户也可以为这个过滤器 指定-
个待移除 单词的列表。
什么是停用词
?
停用词是指在信息检索中,为节省存储空间和提高搜索效率,在处理自然语 言数据(或文本)之前或之后会自动过滤掉某些字或词,这些字或词即被称为 Stop Words(停用词)。
停用词
(Stop Words)
大致可分为如下两类:
1
、使用十分广泛,甚至是过于频繁的一些单词。比如英文的“
i
”、“
is
”、 “what
”,中文的“我”、“就”之类词几乎在每个文档上均会出现,查询这样 的词搜索引擎就无法保证能够给出真正相关的搜索结果,难于缩小搜索范围提高 搜索结果的准确性,同时还会降低搜索的效率。因此,在真正的工作中,Google 和百度等搜索引擎会忽略掉特定的常用词,在搜索的时候,如果我们使用了太多
的停用词,也同样有可能无法得到非常精确的结果,甚至是可能大量毫不相关的 搜索结果。
2
、文本中出现频率很高,但实际意义又不大的词。这一类主要包括了语气 助词、副词、介词、连词等,通常自身并无明确意义,只有将其放入一个完整的 句子中才有一定作用的词语。如常见的“的”、“在”、“和”、“接着”之类。 下面是英文的默认停用词列表: a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or; such,
that, the, their;, then,there, these, they, this, to, was, will, with
系统内置的停止词如下: 种语言中常见的停止词。这些内置的停止词如下:
_arabic_
,
- armenian_
,
_ basque
,
_ bengali 1
,
_ brazilian
,
_ bulgarian_,_
catalan _
,
_czech_,_ danish_
,
_ _dutch_
,
english_
,
finnish_, french_ _
,
_galician
_
,
german_,_greek._hindi_,_ hungarian_
,
_ indonesian_
,
_ irish_
,
_ _italian_
,
_ latvian_,_norwegian_,_ persian_
,
_portuguese_,_ romanian_,_ russian_,-
sorani_,- spanish_,_ swedish_
,
_thai_
,
_turkish_
5.
截断分词过滤器、修剪分词过滤器和限制分词数量过滤器
下面
3
个分词过滤器,通过某种方式限制分词流。
■截断分词过滤器
(
truncate
token filter )
允许你通过定制配置中的
length
参 数,截断超过一定长度的分词。默认截断多于 10
个字符的部分。
■修剪分词过滤器
(
trim
token filter )
删除
1
个分词中的所有空白部分。例如, 分词" foo "
将被转变为分词
foo
。
■限制分词数量分词过滤器
(
limit
token count token filter)
限制了某个字段可 包含分词的最大数量。例如,如果创建了一个定制的分词数量过滤器,限制是 8, 那么分词流中只有前 8
个分词会被索引。这个设置使用
max_ token_ count
参数,
默认是
1 (
只有
1
个分词会被索引
)
。
常用内置分析器
1.
标准分析器
当没有指定分析器的时候,标准分析器
( standardanalyzer)
是文本的默认分析 器。它综合了对大多欧洲语言来说合理的默认模块,它没有字符过滤器,包括标 准分词器、小写转换分词过滤器和停用词分词过滤器(默认为_none_
,也就是不
去除停止词)。这里只需要记住,如果不为某个字段指定分析器,那么该字段就 会使用标准分析器。可配置的参数如下:
max_token_length
,默认值
255
,表示词项最大长度,超过这个长度将按该 长度分为多个词项 stopwords,默认值_none_
,表示分析器使用的停止词数组,可使用内置停 止词列表,比如_english_
等 stopwords_path 停止词文件路径
2.
简单分析器
简单分析器
( simple analyzer)
就是那么简单
!
它只使用了小写转换分词器,这 意味着在非字母处进行分词,并将分词自动转变为小写。这个分析器对于亚洲语 言来说效果不佳,因为亚洲语言不是根据空白来分词,所以请仅仅针对欧洲语言 使用它。
3.
空白分析器
空白分析器
( whitespace analyzer )
什么事情都不做
,
只是根据空白将文本切分 为若干分词。
4.
停用词分析器
停用词分析器
( stop analyzer )
和简单分析器的行为很相像,只是在分词流中 额外地过滤了停用词。
5.
关键词分析器
关键词分析器
( keyword analyzer )
将整个字段当作一个单独的分词。
6.
模式分析器
模板分析器
( pattern analyzer )
允许你指定一个分词切分的模式。 但是,由 于可能无论如何都要指定模式,通常更有意义的做法是使用定制分析器,组合现 有的模式分词器和所需的分词过滤器。
7.
雪球分析器
雪球分析器
( snowball analyzer )
除了使用标准的分词器和分词过滤器
(
和标 准分析器一样),
也使用了小写分词过滤器和停用词过滤器。它还使用了雪球词干 器对文本进行词干提取。
试试自定义分析器
业务需求如下: 去除所有的 HTML
标签 将 &
替换成
and
,使用一个自定义的
mapping
字符过滤器
使用
standard
分词器分割单词 使用 lowercase
分词过滤器将词转为小写 用 stop
分词过滤器去除一些自定义停用词。
PUT pattern_custom
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"char_filter": [
"html_strip",
"&_to_and"
],
"filter": [
"lowercase",
"my_stopwords"
],
"tokenizer": "standard",
"type": "custom"
}
},
"char_filter": {
"&_to_and": {
"mappings": [
"&=>and"
],
"type": "mapping"
}
},
"filter": {
"my_stopwords": {
"stopwords": [
"king",
"james"
],
"type": "stop"
}
}
}
}
}
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"char_filter": [
"html_strip",
"&_to_and"
],
"filter": [
"lowercase",
"my_stopwords"
],
"tokenizer": "standard",
"type": "custom"
}
},
"char_filter": {
"&_to_and": {
"mappings": [
"&=>and"
],
"type": "mapping"
}
},
"filter": {
"my_stopwords": {
"stopwords": [
"king",
"james"
],
"type": "stop"
}
}
}
}
}
POST pattern_custom/_analyze
{
"analyzer": "my_analyzer",
"text": "<br> I & Lison & king & James are handsome<br>"
}
中文分析器
上面的分析器基本都是针对英文的,对中文的处理不是太好,比如
分析后的结果是:
Standard
分析器把中文语句拆分为一个个的汉字,并不是太适合。这时候, 就需要中文分析器。
中文分析器有很多,例如
cjk
,
ik
等等,我们选用比较有名的
ik
作为我们的 中文分析器。
安装
进入
elasticsearch
目录下的
plugins
目录,并执行
./elasticsearch-plugin install
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.7.0/elasti
csearch-analysis-ik-7.7.0.zip
如果询问你“
Continue with installation?
”,当然继续进行。 安装完成后,必须重启 elasticsearch
。
使用
IK
分词器有两种分词效果,一种是
ik_max_word
(最大分词)和
ik_smart
(最 小分词)ik_max_word: 会将文本做最细粒度的拆分,比如会将
基于全文的搜索
了解了文本分析以后,就可以学习基于全文的搜索了,这里就需要用到 match 系列查询。
match
查询
比如说:
"query":{
"match":{
"elk":"Elasticsearch LogStash Kibana"
}
查询字符串是“
Elasticsearch LogStash Kibana
”,被分析器分词之后,产生三 个小写的单词:elasticsearch logstash kibana
,然后根据分析的结果构造一个布尔 查询,默认情况下,引擎内部执行的查询逻辑是:只要 elk
字段值中包含有任意 一个关键字 elasticsearch
或
logStash
或
kibana
,那么返回该文档,相对于的伪代 码是:
if(
doc. elk.contains(elasticsearch)
||doc. elk.contains(logstash)
||doc. elk.contains (kibana)
)
return doc ;
匹配查询的行为受到两个参数的控制:
operator
:表示单个字段如何匹配查询条件的分词
minimum_should_match
:表示字段匹配的数量
通过调整
operator
和
minimum_should_match
属性值,控制匹配查询的逻 辑条件,进而控制引擎返回的结果。默认情况下 operator
的值是
or
,在构造查 询时设置分词之间的逻辑运算符,如果设置为 and
,那么引擎内部执行的查询逻
辑是:
if(
doc. elk.contains(elasticsearch)
&&doc. elk.contains(logstash)
&&doc. elk.contains (kibana)
)
return doc ;
对于
minimum_should_match
属性值,默认值是
1
,如果设置其值为
2
,表 示分词必须匹配查询条件的数量为 2
,这意味着,只要文档的
elk
字段包含任意
两个关键字,就满足查询条件,但是如果文档中只有
1
个关键字,这个文档就不 满足条件。比如:
POST /kibana_sample_data_logs/_search
{
"query": {
"match": {
"message": "firefox chrome"
}
}
}
检索包含
firefox
或
chrome
的文档,如果改为:
POST /kibana_sample_data_logs/_search
{
"query": {
"match": {
"message": {
"query": "firefox chrome",
"operator": "and"
}
}
}
}
"query": {
"match": {
"message": {
"query": "firefox chrome",
"operator": "and"
}
}
}
}
则不会有任何文档返回,因为没有文档的
message
字段既包含
firefox
又包 含 chrome
。同样:
POST /kibana_sample_data_logs/_search
{
"query": {
"match": {
"message": {
"query": "firefox chrome",
"minimum_should_match": 2
}
}
}
"query": {
"match": {
"message": {
"query": "firefox chrome",
"minimum_should_match": 2
}
}
}
}
也不会任何文档返回,原因也是一样的,因为没有文档的
message
字段既包 含 firefox
又包含
chrome
。
multi_match
查询
多个字段上执行匹配相同的查询,叫做
"multi_match"
查询。比如:
POST /kibana_sample_data_flights/_search
{
"query": {
"multi_match": {
"query": "AT",
"fields": [
"DestCountry",
"OriginCountry"
]
}
}
}
"query": {
"multi_match": {
"query": "AT",
"fields": [
"DestCountry",
"OriginCountry"
]
}
}
}
请求将同时检索文档中
DestCountry
和
OriginCountry
这两个字段,只要有一
个字段包含
AT
词项该文档就满足查询条件。
match_phrase
查询
当你希望寻找邻近的单词时,
match_phrase
查询可以帮你达到目的。比如:
假设我们要找到
title
字段包含这么一段文本“
quick brown fox
”的文档,然
后我们用
GET /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": "quick brown fox"
}
}
}
"query": {
"match_phrase": {
"title": "quick brown fox"
}
}
}
match_phrase
查询首先解析查询字符串来产生一个词条列表。然后会搜索所
有的词条,但只保留包含了所有搜索词条的文档,并且词条的位置要邻接。
但是对于
GET /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": "quick fox"
}
}
}
"query": {
"match_phrase": {
"title": "quick fox"
}
}
}
这个查询查询不会匹配我们的任何文档,因为没有文档含有邻接在一起的
quick
和
fox
词条。也就是说,匹配的文档必须满足:
1
、
quick
、
brown
和
fox
必须全部出现在
title
字段中。
2
、
brown
的位置必须比
quick
的位置大
1
。
3
、
fox
的位置必须比
quick
的位置大
2
。
如果以上的任何一个条件没有被满足,那么文档就不能被匹配。 精确短语(Exact-phrase)
匹配也许太过于严格了。也许我们希望含有
"quick brown fox"的文档也能够匹配
"quick fox"
查询,即使位置并不是完全相等的。 我们可以在短语匹配使用 slop
参数来引入一些灵活性:
GET /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": {
"query": "quick fox",
"slop": 1
}
}
}
}
"query": {
"match_phrase": {
"title": {
"query": "quick fox",
"slop": 1
}
}
}
}
slop
参数缺省为
0
,它告诉
match_phrase
查询词条能够最远相隔多远时仍然 将文档视为匹配。相隔多远的意思是,你需要移动一个词条多少次来让查询和文 档匹配?比如这样一段文本:hello world, java is very good, spark is also very good.
使用
match_phrase
搜索
java spark
搜不到 如果我们指定了 slop
,那么就允许
java spark
进行移动,来尝试与
doc
进
行匹配
上面展示了,当固定第一个
term
的时候,后面的
teram
经过移动直到匹 配上搜索词的经过这个移动的次数就是 slop
,实际例子如下:
POST /kibana_sample_data_logs/_search
{
"query": {
"match_phrase": {
"message": "firefox 6.0a1"
}
}
}
match_phrase_prefix
查询
被称为基于前缀的短语匹配,比如:
{
"match_phrase_prefix" : {
"brand" : "johnnie walker bl"
}
}
这种查询的行为与
match_phrase
查询一致,不同的是它将查询字符串的最 后一个词作为前缀使用,换句话说,可以将之前的例子看成如下这样: johnnie
跟着
walker
跟着以
bl
开始的词
或者可以干脆理解为:
"johnnie walker bl*" 与 match_phrase
一样,它也可以接受
slop
参数(参照
slop
)让相对词 序位置不那么严格:
{
"match_phrase_prefix": {
"brand": {
"query": "walker johnnie bl",
"slop": 10
}
}
}
"match_phrase_prefix": {
"brand": {
"query": "walker johnnie bl",
"slop": 10
}
}
}
prefix
查询存在严重的资源消耗问题,短语查询的这种方式也同样如此。前 缀 a
可能会匹配成千上万的词,这不仅会消耗很多系统资源,而且结果的用处 也不大。可以通过设置 max_expansions
参数来限制前缀扩展的影响,一个合理的值 是是 50
,这也是系统默认的值:
{
"match_phrase_prefix": {
"brand": {
"query": "johnnie walker bl",
"max_expansions": 50
}
}
}
"match_phrase_prefix": {
"brand": {
"query": "johnnie walker bl",
"max_expansions": 50
}
}
}
实际例子:
POST /kibana_sample_data_logs/_search
{
"query": {
"match_phrase_prefix": {
"message": "firefox 6.0"
}
}
}
"query": {
"match_phrase_prefix": {
"message": "firefox 6.0"
}
}
}
模糊查询、纠错与提示器
编辑距离算法
在
Elasticsearch
基于全文的查询中,除了与短语相关的查询以外,其余查询 都包含有一个名为 fuzziness
的参数用于支持模糊查询。
Elasticsearch
支持的模糊 查询与 SQL
语言中模糊查询还不一样,
SQL
的模糊查询使用“
% keyword%"
的形 式,效果是查询字段值中包含 keyword
的记录。
Elaticsearch
支持的模糊查询比 这个要强大得多,它可以根据一个拼写错误的词项匹配正确的结果,例如根据 firefix 匹配
firefox
。在自然语言处理领域,两个词项之间的差异通常称为距离或 编辑距离,距离的大小用于说明两个词项之间差异的大小。计算词项 编辑距离的算法有多种,在 Elasticsearch
中主要使用
Levenshtein
和
NGram 两种。其他与此相关的算法也都是在这两种算法基础上进行的改造,基本思想都 是一致的。所以理解这两个算法的核心思想是学习这部分内容的关键。
Levenshtein
与
NGram
Levenshein算法是前苏联数学家
Vladimir Levenshein
在
1965
年开发的一套算 法, 这个算法可以对两个字符申的差异程度做量化。量化结果是一一个正整数, 反映的是一个字符申变成另一个字符申最少需要多少次的处理。由于 Levenshtein 算法是最为普遍接受的编辑距离算法,所以在很多文献中如果没有特殊说明编辑 距离算法就是指 Levenshtein
算法。 在 Levenshtein
算法中定义了三种字符操作,即替换、插人和删除,后来又 由其他科学家补充了一个换位操作。在转换过程中,每执行次操作编辑距离就加
1
, 编辑距离越大越能说明两个字符串之间的差距大。 比如从 firefix
到
firefox
需要将“
i"
替换成“
o
”, 所以编辑距离为
1;
而从
fax
到
fair
则需要将“
x
”替换为“
i"
并在结尾处插人“
r
”,所以编辑距离为
2
。显然 在编辑距离相同的情况下,单词越长错误与正确就越接近。比如编辑距离同样为
2
的情况下,从
fax
到
fair
与从
elascsearxh
到
elasticsearch,
后者
elastesearsh
是由 拼写错误引起的可能性就更大此。所以编辑距离这种量化标准一般还需要与单词 长度结合起来 明虑,在一些极端情况下编辑距离还应该设置为 0
,比如像
at
、 on 这类长度只有
2
的短单词。 NGram 一般是指
N
个连续的字符,具体的字符个数被定义为
NGram
的
size
。 size 为
1
的
NGram
称为
Unigram, size
为
2
时称为
Bigram,
而
size
为
3
时则称为 Trigram。如果
NGram
处理的单元不是字符而是单词,一般称之为
Shingle
。使用 NGram 计算编辑距离的基本思路是让字符串分解为
NGram
,然后比较分解后共 有 NGram
的数量。假设有
a
、
b
两个字符申,则
NGram
距离的具体运算公式为 ngram( a )+ngram(b) -2 * ngram(a)∩
ngram( b) 式中,ngram(a)
和
ngram(b)
代表
a
、
b
两个字符串
NGram
的数量
; ngram(a)
∩ ngram(b)则是两者共有
NGram
的数量。
例如按
Bigram
处理
firefix
和
firefox
两个单词,分别为“
fi,ir,re, ef, fi,ix
”和“
fi
,
ir, re, ef, fi, ox"
。 那么两个字符申的
Bigram
个数都为
6
,而共有
Bigram
为
4
,则 最终 NGram
距离为
6+6-2x4=4
。 在应用上,Levenshtein
算法更多地应用于对单个词项的模糊查询上,而 NGram 则应用于多词项匹配中。
Elasticseareh
同时应用了两种算法。
模糊查询
返回包含与搜索字词相似的字词文档;为了找到相似的术语,
fuzzy
查询将 在指定的编辑距离内创建一组搜索词的所有可能的变体或扩展。查询然后返回每 个扩展的完全匹配。
比如:
get kibana_sample_data_logs/_search
{
"query": {
"fuzzy": {
"message": {
"value": "firefix",
"fuzziness": "1"
}
}
}
}
"query": {
"fuzzy": {
"message": {
"value": "firefix",
"fuzziness": "1"
}
}
}
}
我们想找到文档中
message
字段包含
firefox
,而查询条件中给出的是
firefix
, 因为两者的编辑距离为 1
,所以包含
firefox
的文档依然可以找到,但是,如果使 用 firefit
,因为编辑距离为
2
,则不会找到任何文档。
相关的参数有:
value
,必填项,希望在
field
中找到的术语
fuzziness
,选填项,匹配允许的最大编辑距离;可以被设置为“
0
”, “
1
”, “2
”或“
auto
”。“
auto
”是推荐的选项,它会根据查询词的长度定义距离。 max_expansions,选填项,创建的最大变体项,默认为
50
。应该避免使用较 大的值,尤其是当 prefix_length
参数值为
0
时,如果过多,会影响查找性能。 prefix_length,选填项,创建扩展时保留不变的开始字符数。默认为
0
transpositions
,选填项,指示编辑是否包括两个相邻字符串的转置(
ab
→
ba
)。 默认为 true
。
纠错与提示器
纠错是在用户提交了错误的词项时给出正确词项的提示,而输人提示则是在 用户输人关键字时给出智能提示,甚至可以将用户未输人完的内容自动补全。大 多数互联网搜索引擎都同时支持纠错和提示的功能,比如在用户提交了错误的搜 索关键字时会提示:
“ 你是不是想查找
....
”
.
而在用户输人搜索关键字时还能自动 弹出提示框将用户可能要输人的内容全都列出来供用户选择。
Elasticsearch
也同时支持纠错与提示功能,由于这两个功能从实现的角度来 说并没有本质区别,所以它们都由一种被称为提示器或建议器( Suggester)
的特殊 检索实现。由于输人提示需要在用户输人的同时给出提示词,所以这种功能要求 速度必须快,否则就失去了提示的意义。在实现上,输人提示是由单独的提示器 完成。而在使用上,提示器则是通过检索接口_ search
的一个参数设置,
例如
:
POST /kibana_sample_data_logs/_search?filter_path=suggest
{
"suggest": {
"msg-suggest": {
"text": "firefit chrom",
"term": {
"field": "message"
}
}
}
}
"suggest": {
"msg-suggest": {
"text": "firefit chrom",
"term": {
"field": "message"
}
}
}
}
在示例中,
search
接口的
suggest
参数中定义了一个提示
msg- suggest,
并通 过 text
参数给出需要提示的内容。另一个参数
term
实际上是一种提示器的名称, 它会分析 text
参数中的字符串并提取词项,再根据
Levenshtein
算法找到满足编 辑距离的提示词项。所以在返回结果中会包含一个 suguggest
字段,其中列举了 依照 term
提示器找到的提示词项
:
Elaticearch
提供了三种提示器,它们在本质上都是基于编辑距离算法。下面 就来看看这此提示器如何使用。
term
提示器
在示例中使用的提示器就是
term
提示器,这种提示器默认使用的算法是称 为 internal
的编辑距离算法。
intermal
算法本质上就是
Levenshtein
算法,但根 据 Elasticsearch
索引特征做了一些优化而效率更高,可以通过
string _distance
参 数更改算法。
term
提示器使用的编辑距离可通过
max_ edits
参数设置,默认值为
2
。
phrase
提示器
terms
会将需要提示的文本拆分成词项,然后对每一个词项做单独的提示, 而 phrase
提示器则会使用整个文本内容做提示。所以在
phrase
提示器的返回结 果中,不会看到一个词项一个词项的提示,而是针对整个短语的提示。但从使用 的角度来看几乎是一样的,例如:
POST /kibana_sample_data_logs/_search
{
"suggest": {
"msg-suggest": {
"text": "firefix with chrime",
"phrase": {
"field": "message"
}
}
}
}
}
"suggest": {
"msg-suggest": {
"text": "firefix with chrime",
"phrase": {
"field": "message"
}
}
}
}
}
但不要被
phrase
提示器返回结果欺骗,这个提示器在执行时也会对需要提 示的文本内容做词项分析,然后再通过 NGram
算法计算整个短语的编辑距离。 所以本质上来说,phrase
提示是基于
term
提示器的提示器,同时使用了 Levenshtein 和
NGram
算法。
completion
提示器
completion
提示器一般应用于输人提示和自动补全,也就是在用户输人的同 时给出提示或补全未输入内容。这就要求 completion
提示器必须在用户输人结 束前快速地给出提示,所以这个提示器在性能上做了优化以达到快速检索的目的。 首先要求提示词产生的字段为 completion
类型,这是一种专门为
completion 提示器而设计的字段类型,它会在内存中创建特殊的数据结构以满足快速生成提 示词的要求。例如在示例中创建了 aricles
索引,并向其中添加了
1
份文档
:
PUT articles
{
"mappings": {
"properties": {
"author": {
"type": "keyword"
},
"content": {
"type": "text"
},
"suggestions": {
"type": "completion"
}
}
}
}
"mappings": {
"properties": {
"author": {
"type": "keyword"
},
"content": {
"type": "text"
},
"suggestions": {
"type": "completion"
}
}
}
}
POST articles/_doc/
{
"author":"taylor",
"content":"an introduction of elastic stack and elasticsearch",
"suggestions":{
"input":["elastic stack", "elasticsearch"],
"weight":10
}
}
POST articles/_doc/
{
"author": "taylor",
"content": "an introduction of elastic stack and elasticsearch",
"suggestions": [
{
"input": "elasticsearch",
"weight": 30
},
{
"input": "elastic stack",
"weight": 1
}
]
}
"author": "taylor",
"content": "an introduction of elastic stack and elasticsearch",
"suggestions": [
{
"input": "elasticsearch",
"weight": 30
},
{
"input": "elastic stack",
"weight": 1
}
]
}
在向
completion
类型的字段添加内容时可以使用两个参数,
input
参数设置 字段实际保存的提示词;
而
weight
参数则设置了这些提示词的权重,权重越高它 在返回的提示词中越靠前。在示例 5-29
中给出了两种设置提示词权重的方式, 第一种是将一组提示词的权重设置为统一值,另一种则是分开设置它们的权重值。需要注意的是,
completion
类型字段保存的提示词是不会分析词项的,比如示 例 5-29
中的“
elastic stack
”并不会拆分成两个提示词,而是以整体出现在提示 词列表中。 completion 提示器专门用于输人提示或补全,它根据用户已经输人的内容提 示完整词项,
所以在
completion
提示器中没有
text
参数而是使用
prefix
参数。例
如
:
POST articles/_search
{
"_source": "suggest",
"suggest": {
"article_suggestion": {
"prefix": "ela",
"completion": {
"field": "suggestions"
}
}
}
}
"_source": "suggest",
"suggest": {
"article_suggestion": {
"prefix": "ela",
"completion": {
"field": "suggestions"
}
}
}
}
总结一下,
term
和
phrase
提示器主要用于纠错,
term
提示器用于对单个词 项的纠错而 phrase
提示器则主要针对短语做纠错。
completion
提示器是专门用 于输人提示和自动补全的提示器,在使用上依赖前缀产生提示并且速度更快。
今天分享到此结束,下一篇分析相关性检索和组合查询,敬请期待!