ES在搜索和数据分析中的应用越来越广泛,在之前项目中对ES的使用有些心得,最近有不少朋友和同事都问到了ES,刚好最近也有些时间,所以打算通过8~10篇文章介绍下ES.(其实我也不知道最终会写下多少篇),为了保持阅读的连贯性,可以先看前面几篇文章:
一.概要
本篇文章会介绍文本搜索,文本搜索不同于keyword的精确匹配,文本搜索包括两个核心,一个是Lucene(全文搜索),另外一个是分析器.Lucene将文本进行倒排索引的创建,分析器负责对文本进行分词和按规则处理.这篇文章主要是讨论分析器.并且会提到分析器插件的使用.
分析器主要是做三件事情:
1)字符过滤:去掉文本中一些无关紧要的字词,符号.
2)分词:英文可以按照单词进行分词,但是中文因为有词语这概念,所以需要进行特别的分词,这里将会引入2个插件.
3)分词过滤:分词之后,有些词语也是无关紧要的,所以把那一部分也去掉.
ES在分词之后,会对词语与文本建立倒排索引,并将词语与文本建立关系,指定词语出现在文本的频次与位置.基于这些特征ES在使用match进行文本搜索时,它的大概步骤是这样的:
1) 使用分析器对需要搜索的文本进行分词;
2) 在得到分词之后,使用term对倒排索引中的文本分词集合进行查找;
3) ES根据查询文本的相关度进行权重打分(分词出现的频率,出现的位置,词语相关度等);
4) 权重打分之后按照得分从高到低排序并输出.
二.分析器
ES的分析器包括三个部分: 字符过滤器,分词器,分词过滤器.
在创建或者更新文档是,会启动分析器对文本进行分词,在查询文档时,会启动分析器对查询文本进行分词.一般而言在创建/更新文档中使用的分析器和查询使用的分析器需要统一,这样是在同一个标准下进行分词.
2.1 字符过滤器
分析器的处理过程就像是一颗大白菜在厨房加工的过程,字符过滤器就相当于大白菜处理的第一道工序: 去除不要的菜叶.
字符过滤器会把文本中一些不需要的字符删除或者替换,例如一些特殊符号,html标签等对搜索没有意义的内容.相当于对文本(大白菜)进行粗加工.
ES内置了三种字符过滤器:
1) 映射关系字符过滤器: 根据映射关系,将文本中的内容替换成对应的字符;
2) HTML擦除过滤器,就想百度等搜索引擎一样,爬虫爬下来的内容需要去掉HTML标签.
3) 正则表达式过滤器: 根据定义的正则表达式,去除掉匹配的内容.
2.2 分词器
分词器是按照规则把句子分解成词语,就像大白菜处理的第二道工序:切菜.
对英文进行分词一般好处理,就像剥毛豆,按照一颗颗的豆子进行分割就好了,但是中文博大精深,不能按照单个的字才处理,可能是两个字的词语,也可能是三个,四个或者更多的字组合在一起才是完整的含义,而且组合形式还比较多.所以对于中文需要特殊的插件,这个后面在说.ES内置的分词器一般是针对英文的,有以下几个.
1) 标准分词器: 基于英文语法进行分词;
2) 字母分词器: 基于非字母的标记作为分词依据;
3) 小写字母分词器: 将分词后的结果转换为小写;
4) 空格分词器: 基于空格进行分词.
分词器在分词之后会记录词语的开始和结束的偏移量,以此来记录词语在文本中的位置. 词语在文本中的位置也是搜索权重之一.
2.3 分词过滤器
分词过滤器将分词之后的词语进行再一次筛选,就像大白菜切好之后的第三道工序,再次挑选,将不需要的进一步剔除掉.
ES中内置的分词过滤器有以下三种:
1) Lower Case过滤器: 将分词都转换为小写;
2) stop token过滤器,将设置的停用词剔除掉;
3) 同义词过滤器: 在分词结果中添加同义词.
三.使用分析器
3.1 分析测试API
在ES中使用analyze命令测试分析器的结果.例如:
POST _analyze
{
"analyzer": "standard",
"text": "你可以随意输入一段中文或者英文进行测试,如果是中文会被分解成一个个文字"
}
一般通过这个分析测试API,你可以设施自定义的分析器是否满足结果.
3.2 内置分析器
ES提供了五种内置分析器.
1) standred分析器: 默认分析器,按照空格进行拆分;
2) simple分析器: 按照非字母字符进行词语拆分,并转换为小写;
3) language分析器: 语言分析器;
4) whitespace分析器: 按照空白字符拆分;
5) pattern分析器: 按照正则表达式进行拆分.
上面这几种分析器都不适合中文文本的分析,中文文本在后面我们会需要使用到插件.
3.3 使用分析器
如果在创建索引时不特别指定,ES默认使用standard分析器.或者我们可以通过setting参数指定当前索引所有文本呢字段的分析器,又或者在mapping参数中设置当前字段的分析器.例如下面示例.
PUT /goods4
{
"settings": {
"analysis": {
"analyzer": {"default":{"type":"simple"}}
}
},
"mappings" : {
"properties" : {
"createTime" : {
"type" : "date"
},
"isDeleted" : {
"type" : "boolean"
},
"location" : {
"type" : "geo_point"
},
"price" : {
"type" : "double"
},
"rating" : {
"properties" : {
"bad_rating" : {
"type" : "integer"
},
"good_rating" : {
"type" : "integer"
}
}
},
"stockNum" : {
"type" : "integer"
},
"tag" : {
"type" : "keyword"
},
"title" : {
"type" : "text",
"fields" : {
"title_key" : {
"type" : "keyword"
}
}
},
"updateTime" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss"
},
"info":{
"type": "text",
"analyzer": "whitespace"
}
}
}
}
在上面的代码中,定义了goods4索引默认使用simple分析器,但是在info字段中,我们特别指定了这个字段使用whitespace索引器.
在执行搜索时我们可以额外指定索引器,但是一般没有必要,在进行搜索时使用的索引器应该与创建索引的索引器一致,默认情况就是一致的.
另外我们可以自定义分析器,但是默认的分析器和分析器插件一般都可以满足我们的需求,所以这里就不介绍自定义分析器了.
四.中文分析器
ES内置的分析器无法满足中文文档的分词要求,所以这里需要引用插件来支持中文的分词.常用的第三方中文分析器常用的插件有:IK
4.1 IK分析器
4.1.1 安装IK分析器
重要:要确保IK的版本与ES的版本一致.IK的下载地址是:IK下载
下载好之后在ES的plugins目录下创建IK目录,将下载好的IK解压到这个文件夹,重新启动ES就可以了,这里需要注意的是如果ES所在的父目录包含中文等特殊符号的话启动安装插件之后的ES会出现问题(包括空格也不行,例如:Program Files) 如果有特殊符号现在可以换一个目录.
4.1.2 使用IK分析器
IK分析器包含ik_smart和ik_max_word两个子分析器.例如下面的代码使用_analyze分析器进行分析.
POST _analyze
{
"analyzer": "ik_max_word",
"text": ["正宗山东烟台红富士又大又甜 顺丰冷链物流"]
}
ik_smart的用法类似,但两者的作用不一样,ik_max_word对文本分得更细,会切割出更多的词语.
在上面的分析器中会把"顺丰"分割成两个词语,因为分析器并不认识"顺丰",我们可以自己创建字典表,在plugins\ik\config文件夹下创建my.dic文件,并在文件中输入"顺丰","正宗",每个词语一行,然后再修改IK分析器的配置文件:config/IKAnalyzer.cfg.xm,在里面添加我们的字典词语.
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">my.dic</entry>
最后重启ES,这次看到的 "顺丰","正宗"两个词语就没有被再拆分了.
下面的代码,在定义索引的时候医用IK分析器.
PUT /goods
{
"mappings": { //映射
"properties": { //属性
"title":{ //将title定义为多字段
"type": "text",
"analyzer": "ik_max_word", //指定这个字段的分析器
"fields": {"title_key":{"type":"keyword"}} //title将建立text类型和keyword类型两个索引
},
"price":{"type": "double"}, //价格
"stockNum":{"type": "integer"}, //库存
"createTime":{"type": "date"}, //创建日期
"updateTime":{"type": "date", "format": "yyyy-MM-dd HH:mm:ss"}, //这里创建了两种日期格式
"tag":{"type": "keyword"}, //标签,这里考虑存入数组
"rating":{ //评价:这里设置为对象类型
"properties": { //对象类型这里需要使用到属性
"good_rating":{"type":"integer"},
"bad_rating":{"type":"integer"}
}
},
"location":{"type":"geo_point"}, //地理位置,其实这是一个对象类型
"isDeleted":{"type":"boolean"} //是否删除
}
}
}
}
五.同义词
如果你做过百度或者淘宝的搜索肯定对同义词了解很多,举例来说就是不同的词语其实在大多数语境中表达的是同一个意思,例如番茄=西红柿,学院=学校,等等.
那么在用户进行搜索的时候,很明显,他可能希望把这些同义词也搜索出来.在ES中使用同义词有两种情况,一种是在建立索引的时候进行分词,将文档中的分词结果建立同义词倒排索引,另外一种是在搜索时通过search_analyzer查询分析器使用同义词.
通常在一个文档中同义词的数量会比较多,所以通常将同义词保存在ES安装目录的config目录或者其子目录下.接下来我们在这个目录创建我们的字典表文件:mydict.dict,本案例输入下面的内容:
西红柿,番茄
学校,学院
衣服,服装
西服,西装
然后在创建索引的时候我们可以指定同义词,示例代码如下:
PUT /goods1
{
"settings": {
"analysis": {
"filter": {
"ik_synonyms_filter":{ //定义名为ik_synonyms_filter的过滤器
"type":"synonym_graph", //指定ES内置的分词过滤器
"synonyms_path":"mydict/mydict.dict" //指定同义词字典路径
}
},
"analyzer": {
"ik_analyzer_graph":{ //定义一个名为ik_analyzer_graph的分析器
"tokenizer":"ik_max_word", //指定分词器
"filter":["lowercase","ik_synonyms_filter"] //指定两个过滤器,其中一个是上面定义的
}
}
}
},
"mappings": {
"properties": {
"title":{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_analyzer_graph" //指定搜索时使用的分析器,与创建时的分析器一致
}
}
}
}
六.停用词
停用词就是那些要被剔除掉的词,这些词通常对搜索没有贡献,例如"又大又甜的苹果",这里的"又"和"的"这两个词对搜索没有贡献,那么在进行分词的时候需要将这些词剔除掉.我们可以在网络上获取停用词.
中文停用词:Chinese stopwords
英文停用词:Stopwords
在前面提到的IK分析器的分词只有英文的停用词,如果需要用到中文的停用词需要进行一些设置.
1) 创建停用词文件:首先在ES目录/plugins/ik-analysis/config/目录下创建stopword目录,并在下面创建my_stopword.dict文件.
2) 添加停用词:将上面网站上的中文停用词拷贝到my_stopword.dict文件,当然你也可以手动添加一些.
3) 修改IK的IKAnalyzer.cfg.xml配置文件:内容如下:
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword/my_stopword.dict</entry>
4) 重启ES,执行下面的代码,我们可以看到"的"这个词就并停止检索了
GET _analyze
{
"analyzer": "ik_max_word",
"text": ["在山东有一个生产全国最甜的红富士苹果的农庄"]
}
七.拼音搜索
7.1 拼音插件的安装
拼音搜索需要安装拼音分析器插件,这个插件的使用需要分一下几个步骤:
1) 下载拼音搜索插件,并将这个文件解压到一个方便查找的文件夹目录里面,下载地址: GitHub - medcl/elasticsearch-analysis-pinyin at v8.8.1
2) 因为版本与我们使用的ES版本不匹配,所以需要修改pom.xml文件,主要是修改版本号.
<elasticsearch.version>8.8.2</elasticsearch.version>
<maven.compiler.target>1.8</maven.compiler.target>
我们用的是8.8.2,所以这个版本号也需要改为8.8.2
3) 运行cmd命令,转到当前文件夹,运行mvn install 对这个文件进行编译.编译会需要一些时间.
4) 拷贝程序: 将编译好之后的target\releases\elasticsearch-analysis-pinyin-8.8.2.zip文件解压并拷贝到ES安装目录下的:plugins\pinyin.
5) 重启ES,正常启动后表示pinyin插件已经安装成功.
7.2 使用拼音分析器
拼音分析器的名称为pinyin,下面代码演示了使用这个分析器.
GET _analyze
{
"analyzer": "pinyin",
"text": ["在山东有一个生产全国最甜的红富士苹果的农庄"]
}
可以在定义索引的时候指定拼音分析器,如下面的用法.
PUT /goods2
{
"settings": {
"analysis": {
"filter": { //过滤器
"pinyin_filter": { //自定义名为pinyin_filter的过滤器
"type": "pinyin", //类型是拼音
"keep_first_letter":true, //保留拼音首字母
"key_full_pinyin":false, //不保留全拼
"keep_none_chinese":true //不保留中文
}
},
"analyzer": { //分析器
"ik_pinyin_analyzer":{ //分析器的名字
"tokenizer":"ik_max_word", //使用ik_max_word分词器
"filter":["pinyin_filter"] //使用自定义的pinyin_filter分词过滤器
}
}
}
},
"mappings": {
"properties": {
"title":{
"type": "text",
"analyzer": "ik_pinyin_analyzer"
}
}
}
}
在进行搜索时,我们使用sd这两字母进行搜索,就可以匹配山东这两个字.
GET /goods2/_search
{
"query": {
"match": {
"title": "SD"
}
}
}
八.拼音纠错
在上面的搜索中,会自动进行拼音纠错功能,例如我们搜索"平果"也可以将"苹果"的内容搜索出来.
九.高亮显示
在进行文本搜索时,需要对搜索的内容飘红,这个属于高亮显示,这样可以让用户更加快速的找到自己想要的内容.
对文本进行高亮显示,需要用到highLight参数.例如下面代码:
GET /goods2/_search
{
"query": {
"match": {
"title": "苹果"
}
},
"highlight": {
"fields": {
"title": {} //默认使用<em>标记搜索的文字
}
}
}
在上面highlight属性的title字段为{},这里将使用默认值"<em></em>"标签将高亮部分表示出来,或者也可以使用自定义标签,例如下面的代码:
GET /goods2/_search
{
"query": {
"match": {
"title": "苹果"
}
},
"highlight": {
"fields": {
"title": {
"pre_tags": "<b>", //苹果两个字将使用<b>标记出来.
"post_tags": "</b>"
}
}
}
}
好了,这篇文章比我原计划的要长多了,就到这里了,下一篇再见.