[toc]

1. 什么是analysis?

analysis是Elasticsearch在文档发送之前对文档正文执行的过程,以添加到反向索引中(inverted index)。 在将文档添加到索引之前,Elasticsearch会为每个分析的字段执行许多步骤:

  • Character filtering (字符过滤器): 使用字符过滤器转换字符
  • Breaking text into tokens (把文字转化为标记): 将文本分成一组一个或多个标记
  • Token filtering:使用标记过滤器转换每个标记(大小写转化/删除无用词等等)
  • Token indexing:把这些标记存于index中

文本分词会发生在两个地方:

  • 创建索引:当索引文档字符类型为text时,在建立索引时将会对该字段进行分词。
  • 搜索:当对一个text类型的字段进行全文检索时,会对用户输入的文本进行分词。

2. 主要组成

总体说来一个analyzer可以分为如下的几个部分:

  • 0个或1个以上的character filter

    接收原字符流,通过添加、删除或者替换操作改变原字符流。例如:去除文本中的html标签,或者将罗马数字转换成阿拉伯数字等。一个字符过滤器可以有零个或者多个。

  • 1个tokenizer

    简单的说就是将一整段文本拆分成一个个的词。例如拆分英文,通过空格能将句子拆分成一个个的词,但是对于中文来说,无法使用这种方式来实现。在一个分词器中,有且只有一个tokenizeer

  • 0个或1个以上的token filter

    将切分的单词添加、删除或者改变。例如将所有英文单词小写,或者将英文中的停词a删除等。在token filters中,不允许将token(分出的词)的position或者offset改变。同时,在一个分词器中,可以有零个或者多个token filters.

    单词的过滤顺序按照 filter过滤器的顺序

img

3. 使用分析器

默认ES使用standard analyzer

3.1 Elasticsearch的内置分析器

Analyzer

  • Standard Analyzer - 默认分词器,按词切分,小写处理
  • Simple Analyzer - 按照非字母切分(符号被过滤), 小写处理
  • Stop Analyzer - 小写处理,停用词过滤(the,a,is)
  • Whitespace Analyzer - 按照空格切分,不转小写
  • Keyword Analyzer - 不分词,直接将输入当作输出
  • Patter Analyzer - 正则表达式,默认\W+(非字符分割)
  • Language - 提供了30多种常见语言的分词器
  • Customer Analyzer 自定义分词器

参考链接

Character Filter

character filterlogical namedescription
mapping char filtermapping根据配置的映射关系替换字符
html strip char filterhtml_strip去掉HTML元素
pattern replace char filterpattern_replace用正则表达式处理字符串

Tokenizer

tokenizerlogical namedescription
standard tokenizerstandard
edge ngram tokenizeredgeNGram
keyword tokenizerkeyword不分词
letter analyzerletter按单词分
lowercase analyzerlowercaseletter tokenizer, lower case filter
ngram analyzersnGram
whitespace analyzerwhitespace以空格为分隔符拆分
pattern analyzerpattern定义分隔符的正则表达式
uax email url analyzeruax_url_email不拆分 url 和 email
path hierarchy analyzerpath_hierarchy处理类似 /path/to/somthing样式的字符串

Token Filter

token filterlogical namedescription
standard filterstandard
ascii folding filterasciifolding
length filterlength去掉太长或者太短的
lowercase filterlowercase转成小写
ngram filternGram
edge ngram filteredgeNGram
porter stem filterporterStem波特词干算法
shingle filtershingle定义分隔符的正则表达式
stop filterstop移除 stop words
word delimiter filterword_delimiter将一个单词再拆成子分词
stemmer token filterstemmer
stemmer override filterstemmer_override
keyword marker filterkeyword_marker
keyword repeat filterkeyword_repeat
kstem filterkstem
snowball filtersnowball
phonetic filterphonetic插件
synonym filtersynonyms处理同义词
compound word filterdictionary_decompounder, hyphenation_decompounder分解复合词
reverse filterreverse反转字符串
elision filterelision去掉缩略语
truncate filtertruncate截断字符串
unique filterunique
pattern capture filterpattern_capture
pattern replace filtepattern_replace用正则表达式替换
trim filtertrim去掉空格
limit token count filterlimit限制 token 数量
hunspell filterhunspell拼写检查
common grams filtercommon_grams
normalization filterarabic_normalization, persian_normalization

内置 Analyzer 一览表

3.2 分析器测试

可以通过_analyzerAPI来测试分词的效果。

POST _analyze
{
  "analyzer": "standard",
  "text": "The quick Brown Fox"
}

结果: 按空格切分,所以会有四个词

可以按照下面的规则组合使用:

  • 0个或者多个character filters
  • 一个tokenizer
  • 0个或者多个token filters
POST _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": "The quick Brown Fox"
}

结果: 按空格切分,所以会有四个词,但每个词都是小写

3.3 自定义分析器

当内置的分词器无法满足需求时,可以创建custom类型的分词器。

  • tokenizer:内置或定制的tokenizer.(必须)
  • char_filter:内置或定制的char_filter(非必须)
  • filter:内置或定制的token filter(非必须)
  • position_increment_gap:当值为文本数组时,设置改值会在文本的中间插入假空隙。设置该属性,对与后面的查询会有影响。默认该值为100.
PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer":{
          "type":"custom",
          "tokenizer":"standard",
          "char_filter":["html_strip"],
          "filter":["lowercase","asciifolding"]
        }
      }
    }
  }
}

上面的示例中定义了一个名为my_custom_analyzer的分析器,该分析器的typecustomtokenizerstandardchar_filterhmtl_strip,filter定义了两个分别为:lowercaseasciifolding.

测试一下:

POST my_index/_analyze
{
  "text": "Is this <b>déjà vu</b>?",
  "analyzer": "my_custom_analyzer"
}

结果:

{
  "tokens" : [
    {
      "token" : "is",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "this",
      "start_offset" : 3,
      "end_offset" : 7,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "deja",
      "start_offset" : 11,
      "end_offset" : 15,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "vu",
      "start_offset" : 16,
      "end_offset" : 22,
      "type" : "<ALPHANUM>",
      "position" : 3
    }
  ]
}

4. 分析器作用位置及使用

分析器的使用地方有两个:

  • 创建索引时
  • 进行搜索时

4.1 创建索引时指定分析器

如果设置手动设置了分析器,ES将按照下面顺序来确定使用哪个分析器:

  • 先判断字段是否有设置分析器,如果有,则使用字段属性上的分析器设置
  • 如果设置了analysis.analyzer.default,则使用该设置的分析器
  • 如果上面两个都未设置,则使用默认的standard分析器

为字段指定分析器

PUT my_index
{
  "mappings": {
    "properties": {
      "title":{
        "type":"text",
        "analyzer": "whitespace"
      }
    }
  }
}

设置索引默认分析器

PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "default":{
          "type":"simple"
        }
      }
    }
  }
}

4.2 搜索时如何确定分析器

在搜索时,通过下面参数依次检查搜索时使用的分词器:

  • 搜索时指定analyzer参数
  • 创建mapping时指定字段的search_analyzer属性
  • 创建索引时指定settinganalysis.analyzer.default_search
  • 查看创建索引时字段指定的analyzer属性
  • 如果上面几种都未设置,则使用默认的standard分词器。

搜索时指定analyzer查询参数

GET my_index/_search
{
  "query": {
    "match": {
      "message": {
        "query": "Quick foxes",
        "analyzer": "stop"
      }
    }
  }
}

指定字段的seach_analyzer

PUT my_index
{
  "mappings": {
    "properties": {
      "title":{
        "type":"text",
        "analyzer": "whitespace",
        "search_analyzer": "simple"
      }
    }
  }
}
//  上面指定创建索引时使用的默认分析器为whitespace分词器,而搜索的默认分词器为 simple分词器。

指定索引的默认搜索分词器

PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "default":{
          "type":"simple"
        },
        "default_seach":{
          "type":"whitespace"
        }
      }
    }
  }
}
// 上面指定创建索引时使用的默认分词器为simple分词器,而搜索的默认分词器为whitespace分词器。

5. 常用分析器

5.1 繁简转换分析器

简体和繁体的几个主要情况:

  • 字形差异,汉字字形本身存在着明显差异,如劉与刘,一般是单个字。

  • 词汇的差异,习惯、文化环境造成的用法差异,如滑鼠与鼠标,一般是多字词语。

  • 多种形态共存,有很多代表不同意思的繁体最后简化成了一个字,所以就出现了多音和多义的简体字,如:飆、飈、𩙪都对应于一个简体:飚。

对应第一种,我们可以采用替换的办法,将所有的繁体都替换成对应的简体,在创建索引的时候,就进行标准化,比较好实现,(此文仅针对第一种)

第二种我们可以采用收集相应的词汇转换关系,进行替换,不过因为是多字词语的替换,所以这里面还需要提取处理好分词,不然就有可能替换错误反而引起歧义的问题。

第三种情况,因为繁体和简体是一对多的情况,我们将繁体通过映射表转成简体相对容易,反过来将简体转换成繁体将遇到调整,可能需要结合前后语义来选择正确的繁体。

5.1.1 安装与验证

Github 上的地址是:https://github.com/medcl/elasticsearch-analysis-stconvert/releases

下载之后解压到es的安装目录下的plugins 子目录,重启es即可 (必须选择对应版本)

验证:

image

如果你的集群里面有不止一个 Elasticsearch 的节点,你需要在每一台 Elasticsearch 的实例上执行相同的插件安装,并进行重启让其加载生效。

5.1.2 插件介绍

STConvert 这个插件一共提供了 4 个不同的组件:

  • 一个名为 stconvert 的 Analyzer,可以将简体转换成繁体
  • 一个名为 stconvert 的 Tokenizer,可以将简体转换成繁体
  • 一个名为 stconvert 的 Token Filter,可以将简体转换成繁体
  • 一个名为 stconvert 的 Char Filter,可以将简体转换成繁体

每个组件都可以有以下3个参数用来进行自定义配置,分别是:

  • 参数 convert_type 设置转换的方向,默认是 s2t,表示简体到繁体,如果要将繁体转换为简体,则设置为 t2s
  • 参数 keep_both 用于设置是否保留转换之前的内容,一般来说保留原始内容可以提高我们的搜索命中率(也就是说可以同时搜索繁体和简体),默认是 false,也就是不保留
  • 参数 delimiter 主要是用于,当保留原始内容的时候,如何分割两部分内容,默认的值是逗号 ,

t: Traditional (传统的) => Traditional Chinese (繁体)

s: Simplified (简化的) => Simplified Chinese (简体)

5.1.3 简转繁

简转繁好做,因为插件默认就是做这个的

GET /_analyze
{
  "tokenizer" : "standard",
  "filter" : ["lowercase"],
  "char_filter" : ["stconvert"],
  "text" : "我爱China。"
}

我们指定了 Tokenizer 为 standard,也指定了 lowercase 作为分词之后的 Filter,这样就跟标准的 Standard Analyzer 的分析效果一样了,我们还新增了一个 Char Filter 的参数设置,使用的是我们新安装的简繁体转换插件提供的 stconvert,也就是将简体转成繁体,其结果如下

{
  "tokens": [
    {
      "token": "我",
      "start_offset": 0,
      "end_offset": 1,
      "type": "<IDEOGRAPHIC>",
      "position": 0
    },
    {
      "token": "愛",
      "start_offset": 1,
      "end_offset": 2,
      "type": "<IDEOGRAPHIC>",
      "position": 1
    },
    {
      "token": "china",
      "start_offset": 2,
      "end_offset": 7,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

可以看到,除了和 Standard 分词效果一样的地方以外,我们还额外的将中文字符都转成了繁体, 字转成了 ,而 没有变化,是因为它的繁体也是 。建议我们在真正动手进行自定义分词之前,通过这样的方式先进行分词效果的测试,得到满意的结果之后再进行具体的自定义 Analyzer 的创建工作。

5.1.4 繁转简

创建索引时指定分析器

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
            "tokenizer": "standard",
            "filter": ["lowercase"],
            "char_filter": ["tsconvert"]
        }
      },
      "char_filter": {
        "tsconvert" : {
            "type" : "stconvert",
            "delimiter" : "#",
            "keep_both" : false,
            "convert_type" : "t2s"
        }
      }
    }
  }
}
// delimiter 和 keep_both 属性可以不写

测试:

POST /my_index/_analyze
{
	"analyzer": "my_analyzer",
	"text": "我愛China。"
}

经过执行,得到的分析结果如下:

{
  "tokens": [
    {
      "token": "我",
      "start_offset": 0,
      "end_offset": 1,
      "type": "<IDEOGRAPHIC>",
      "position": 0
    },
    {
      "token": "爱",
      "start_offset": 1,
      "end_offset": 2,
      "type": "<IDEOGRAPHIC>",
      "position": 1
    },
    {
      "token": "china",
      "start_offset": 2,
      "end_offset": 7,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

5.2 Ik分析器

IK分析插件将Lucene IK分析器(http://code.google.com/p/ik-analyzer/)集成到elasticsearch中,支持自定义字典。

5.2.1 安装与验证

Github 上的地址是:https://github.com/medcl/elasticsearch-analysis-ik/releases

下载之后解压到es的安装目录下的plugins 子目录,重启es即可 (必须选择对应版本)

5.2.2 插件介绍

  • Analyzer: ik_smart , ik_max_word ,
  • Tokenizer: ik_smart , ik_max_word

ik_max_wordik_smart 什么区别?

ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;

ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase 查询。

5.2.3 基本使用

使用analyzer:

image

image

使用tokenizer

PUT product
{
  "settings": {
    "analysis": {   
      "analyzer": {
        "my_analyzer": {
          "type": "custom",       
          "filter": [
            "lowercase"
          ],
          "tokenizer": "ik_smart"
        }
      }
    }
  }
  }

5.2.4 自定义分词库

配置 在ik分析器的config目录下的 IKAnalyzer.cfg.xml

内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
 	<!--用户可以在这里配置远程扩展字典 -->
	<entry key="remote_ext_dict">location</entry>
 	<!--用户可以在这里配置远程扩展停止词字典-->
	<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
</properties>

扩展词: 不想被分词的词: 比如, 乔碧罗, 神罗天征等

停止词: 不想要的词,搜索时不会搜它, 比如: 个 ,啊, the , a 等

远程词典具备热更新的能力, 满足以下前两点即可实现热更新

其中 location 是指一个 url,比如 http://yoursite.com/getCustomDict,该请求只需满足以下两点即可完成分词热更新。

  1. 该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。

    写个接口,返回修改时间和ETag即可

    或者用Ng转发静态文件, 会自动加上这个两个配置,当文件改动时,这两个值自动改变

  2. 该 http 请求返回的内容格式是一行一个分词,换行符用 即可。

  3. url返回一个UTF-8格式的文本即可

如果有多个文件,用英文分号隔开;

监控流程

  1. 读取 IKAnalyzer.cfg.xml配置文件,并解析其中的配置

  2. 向词库服务器发送Head请求

  3. 从响应中获取Last- ModifyEtags字段值,判断是否变化

  4. 如果未变化,体眠1min,返回第0步

    ​ 如果有变化,重新加戟词典

  5. 休眠1min,返回第0步

自定义词汇只会对新加的数据生效,不会对存量数据生效,需要更新一次才行

数据在写入时就已分词存储,所以自定义词汇不会对存量数据生效

官方 提供更新方式 POST product/_update_by_query?conflicts=proceed

update_by_query原理

开始时获取一个索引的快照,并且使用内部版本来号来进行更新。这意味着如果文档在获得快照后,对索引处理 过程中版本发生更改,将会发生版本冲突。当快照的版本和索引版本一直时则进行更新,并且递增文档版本号。

当遇到冲突而导致整个更新过程失败时,更新过程是不会回滚的。如果不想因为冲突导致整个更新过程终止,可以在url中添加参数conflicts=proceed。或者在请求body中添加”conflicts”:”proceed”

不光这里可以用,修改es文档也是用_update_by_query

执行结果如图:(消耗时间单位 : 毫秒)

格力的es集群,执行效率大概是2300条/s, 26万数据,大概花了1.7分钟

image

更新可能耗时太久导致客户端连接超时,可以用命令查看任务执行情况(未结束的任务)

GET _tasks?actions=*update*&detailed

5.3 同义词分析器

配置同义词

5.4 拼音分析器

Pinyin Analysis for Elasticsearch

参考链接:

分析器-简书

分析器-博客园

繁简转换

ES的同义词、扩展词、停止词热更新方案

ik分析器-github

ik分析器-简书

ik分析器-csdn

IK分词器原理与源码分析