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>"
      }
    }
  }
}

好了,这篇文章比我原计划的要长多了,就到这里了,下一篇再见.