Elasticsearch分析器

在前面的章节中,我们给大家介绍了索引中的映射类型,也就是每一个字段都有一个类型,比如:long,text,date等。这和我们的数据库非常的相似,那么它的不同之处是什么呢?对了,就是全文索引,在ES当中,只有text类型的字段才会用的全文索引,那么这里就会引出ES中一个非常重要的概念,文本分析器(Text analysis)。

分析器使ES支持全文索引,搜索的结果是和你搜索的内容相关的,而不是你搜索内容的确切匹配。我们用ES官网中的例子给大家举例,假如你在搜索框中输入的内容是Quick fox jumps,你想得到的结果是A quick brown fox jumps over the lazy dog,或者结果中包含这样的词fast fox或foxes leap。

分析器之所以能够使搜索支持全文索引,都是因为有分词器(tokenization),它可以将一句话、一篇文章切分成不同的词语,每个词语都是独立的。假如你在ES索引中添加了一条记录the quick brown fox jumps,而用户搜索时输入的内容是quick fox,并没有完全匹配的内容,但是因为有了分词器,你索引的内容被切分成了不同的、独立的词,用户搜索的内容也会进行相应的切分,所以用户搜索的内容虽然没有完全匹配,但也能够搜索到想要的内容。

分析器除了要做分词,还要做归一化(Normalization)。分词器能够使搜索内容在每一个词上匹配,但这种匹配也只是在字面上进行的匹配。

  • 比如你搜索Quick,但是不能匹配到quick,它们的大小写不同。
  • 比如你搜索fox,但是不能匹配到foxes,它是复数形式。
  • 比如你搜索jumps,不能匹配到leaps,虽然它们是同义词。

为了解决这些问题,分析器要把这些分词归一化到标准的格式。这样我们在搜索的时候就不用严格的匹配了,相似的词语我们也能够检索出来,上面的3种情况,我们也能够搜索出相应的结果。

分析器的组成

分析器,无论是内置的,还是自定义的,都是由3部分组成:字符过滤器(character filters)、分词器(tokenizers)、分词过滤器(token filters)。

字符过滤器

字符过滤器接收最原始的文档,并且可以改变其内容,比如:可以把中文的一二三四五六七八九,变成阿拉伯数字123456789。它还可以过滤html标签,并对其进行转义。还可以通过正则表达式,把匹配到的内容转化成其他的内容。一个分析器可以有多个字符过滤器,也可以没有字符过滤器。

分词器

一个分析器只能有一个确定的分词器,它可以把一句话分成若干个词,比如:空格分词器。当你输入一句话Quick brown fox!,它将被切分成[Quick, brown, fox!]。

分词过滤器

分词过滤器接收分词并且可以改变分词,比如:小写分词过滤器,它将接收到的分词全部转换成小写。助词过滤器,它将删除掉一些公共的助词,比如英语里的 the,is,are等,中文里的的,得等。同义词过滤器,它将在你的分词中,添加相应的同义词。一个分析器可以有多个分词过滤器,它们将按顺序执行。

我们在建立索引和搜索时,都会用的分析器。

配置文本分析器

ES默认给我们配置的分析器是标准分析器。如果标准的分析器不适合你,你可以指定其他的分析器,或者自定义一个分析器。

内置的分词器有:

  • standard:ES默认分词器,按单词分类并进行小写处理
  • simple :按照非字母切分,然后去除非字母并进行小写处理
  • whitespace: 按照空格切分
  • stop :按照停用词过滤并进行小写处理,停用词包括the、a、is
  • keyword: 不进行分词,作为一个整体输出
  • pattern :按照正则表达式进行分词,默认是\W+ ,代表非字母

ES有分析器的api,我们指定分析器和文本内容,就可以得到分词的结果。比如:

POST _analyze
{
  "analyzer": "whitespace",
  "text":     "The quick brown fox."
}

返回的结果如下:

{
    "tokens": [
        {
            "token": "The",
            "start_offset": 0,
            "end_offset": 3,
            "type": "word",
            "position": 0
        },
        {
            "token": "quick",
            "start_offset": 4,
            "end_offset": 9,
            "type": "word",
            "position": 1
        },
        {
            "token": "brown",
            "start_offset": 10,
            "end_offset": 15,
            "type": "word",
            "position": 2
        },
        {
            "token": "fox.",
            "start_offset": 16,
            "end_offset": 20,
            "type": "word",
            "position": 3
        }
    ]
}

我们指定的分析器是空格分析器,输入的文本内容是The quick brown fox.,返回结果是用空格切分的四个词。我们也可以测试分析器的组合,比如:

POST _analyze
{
  "tokenizer": "standard",
  "filter":  [ "lowercase", "asciifolding" ],
  "text":      "Is this déja vu?"
}

我们指定了标准的分词器,小写过滤器和asciifolding过滤器。输入的内容是Is this déja vu?,我们执行一下,得到如下的结果:

{
    "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": 8,
            "end_offset": 12,
            "type": "<ALPHANUM>",
            "position": 2
        },
        {
            "token": "vu",
            "start_offset": 13,
            "end_offset": 15,
            "type": "<ALPHANUM>",
            "position": 3
        }
    ]
}

我们可以看到结果中,is变成了小写,déja变成了deja,最后的?也过滤掉了。

为指定的字段配置分析器

我们在创建映射时,可以为每一个text类型的字段指定分析器,例如:

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

我们在my_index索引中,创建了title字段,它的类型是text,它的分析器是whitespace空格分析器。

为索引指定默认的分析器

如果我们觉得为每一个字段指定分析器过于麻烦,我们还可以为索引指定一个默认的分词器,如下:

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

我们为my_index索引指定了默认的分析器whitespace。这样我们在创建text类型的字段时,就不用为其指定分析器了。

IK中文分词器

IK中文分词器的安装

ES默认是没有IK中文分词器的,我们要将IK中文分词器作为一个插件安装到ES中,安装的步骤也很简单:

1、从GitHub上下载适合自己ES版本的IK中文分词器,地址如下:

https://github.com/medcl/elasticsearch-analysis-ik/releases

我的ES版本是7.10.2,所以下载的ik分词器必须也是7.10.2

2、在我们的ES的插件目录中(${ES_HOME}/plugins)创建ik目录,

3、将我们下载好的IK分词器解压到ik目录,这里我们安装unzip命令,进行解压。

4、重启我们所有的ES服务

IK中文分词器初探

在前面我们访问了ES的分析器接口,指定了分析器和文本的内容,我们就可以看到分词的结果。那么既然我们已经安装了Ik中文分词器,当然要看一下效果了。在看效果之前,我们先要说一下,IK中文分词器插件给我们提供了两个分析器。

  • ik_max_word:会将文本做最细粒度的拆分
  • ik_smart:会做最粗粒度的拆分

我们先看看ik_max_word的分析效果:

POST _analyze
{
  "analyzer": "ik_max_word",
  "text":     "中华人民共和国国歌"
}

我们指定分词器为ik_max_word,文本内容为中华人民共和国国歌。我们看一下分词的结果:

{
    "tokens": [
        {
            "token": "中华人民共和国",
            "start_offset": 0,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "中华人民",
            "start_offset": 0,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 1
        },
        {
            "token": "中华",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "华人",
            "start_offset": 1,
            "end_offset": 3,
            "type": "CN_WORD",
            "position": 3
        },
        {
            "token": "人民共和国",
            "start_offset": 2,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 4
        },
        {
            "token": "人民",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 5
        },
        {
            "token": "共和国",
            "start_offset": 4,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 6
        },
        {
            "token": "共和",
            "start_offset": 4,
            "end_offset": 6,
            "type": "CN_WORD",
            "position": 7
        },
        {
            "token": "国",
            "start_offset": 6,
            "end_offset": 7,
            "type": "CN_CHAR",
            "position": 8
        },
        {
            "token": "国歌",
            "start_offset": 7,
            "end_offset": 9,
            "type": "CN_WORD",
            "position": 9
        }
    ]
}

我们可以看到,分词分的非常细,我们在使用上面的这些进行搜索时,都可以搜索到中华人民共和国国歌这个文本。我们再看一下另外一个分析器ik_smart,

POST _analyze
{
  "analyzer": "ik_smart",
  "text":     "中华人民共和国国歌"
}

我们的文本内容同样是中华人民共和国国歌,看一下分词的效果:

{
    "tokens": [
        {
            "token": "中华人民共和国",
            "start_offset": 0,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "国歌",
            "start_offset": 7,
            "end_offset": 9,
            "type": "CN_WORD",
            "position": 1
        }
    ]
}

同样的文本,使用ik_smart进行分词时,只分成了两个词,和ik_max_word分词器比少了很多。这就是两个分词器的区别,不过这两个分析器都是可以对中文进行分词的。

创建索引时指定IK分词器

既然我们安装了IK中文分词器的插件,那么我们在创建索引时就可以为text类型的字段指定IK中文分词器了。来看看下面的例子:

PUT ik_index
{
    "mappings": {
        "properties": {
            "id": {
                "type": "long"
            },
            "title": {
                "type": "text",
                "analyzer": "ik_max_word"
            }
        }
    }
}

我们创建了索引ik_index,并且为字段title指定了分词器ik_max_word。我们执行一下,创建成功。然后我们再通过GET请求看一下这个索引的映射情况。

GET ik_index/_mapping

返回的结果如下:

{
    "ik_index": {
        "mappings": {
            "properties": {
                "id": {
                    "type": "long"
                },
                "title": {
                    "type": "text",
                    "analyzer": "ik_max_word"
                }
            }
        }
    }
}

我们可以看到title字段的分析器是ik_max_word。

为索引指定默认IK分词器

我们已经给大家介绍了为索引指定默认分词器的方法,这里我们直接把分词器改为IK分词器就可以了,如下:

PUT ik_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "default": {
          "type": "ik_max_word"
        }
      }
    }
  }
}

这样我们在索引中就不用创建每一个字段,可以通过动态字段映射,将String类型的字段映射为text类型,同时分词器指定为ik_max_word。我们试一下,向ik_index索引中添加一条记录。

POST ik_index/_doc/1
{
	"id": 1,
	"title": "大兴庞各庄的西瓜",
	"desc": "大兴庞各庄的西瓜真是好吃,脆沙瓤,甜掉牙"
}

执行成功。我们再执行搜索试一下,如下:

POST ik_index/_search
{
  "query": { "match": { "title": "西瓜" } }
}

我们搜索title字段匹配西瓜,执行结果如下:

{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 0.2876821,
        "hits": [
            {
                "_index": "ik_index",
                "_type": "_doc",
                "_id": "1",
                "_score": 0.2876821,
                "_source": {
                    "id": 1,
                    "title": "大兴庞各庄的西瓜",
                    "desc": "大兴庞各庄的西瓜真是好吃,脆沙瓤,甜掉牙"
                }
            }
        ]
    }
}

我们可以看到刚才插入的那条记录已经搜索出来了,看来我们的IK中文分词器起作用了,而且搜索的结果也符合我们的预期。我们再看看搜索西一个字的时候,能不能搜索到结果

POST ik_index/_search
{
  "query": { "match": { "title": "西" } }
}

执行结果如下:

{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 0,
            "relation": "eq"
        },
        "max_score": null,
        "hits": []
    }
}

并没有搜索出结果,说明在进行分词时,西瓜是作为一个词出现的,并没有拆分成每一个字,这也是符合我们预期的。

内置分词器和自定义分词器

"settings":{
    "analysis": { # 自定义分词
      "filter": {
      	"自定义过滤器": {
            "type": "edge_ngram",  # 过滤器类型
            "min_gram": "1",  # 最小边界 
            "max_gram": "6"  # 最大边界
        }
      },  # 过滤器
      "char_filter": {},  # 字符过滤器
      "tokenizer": {},   # 分词
      "analyzer": {
      	"自定义分词器名称": {
          "type": "custom",
          "tokenizer": "上述自定义分词名称或自带分词",
          "filter": [
            "上述自定义过滤器名称或自带过滤器"
          ],
          "char_filter": [
          	"上述自定义字符过滤器名称或自带字符过滤器"
          ]
        }
      }  # 分词器
    }
}

内置的分词器

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

常见的几个分词器:Standard Analyzer、Simple Analyzer、whitespace Analyzer。

Standard Analyzer(默认)

standard 是默认的分析器。它提供了基于语法的标记化(基于Unicode文本分割算法),适用于大多数语言。

POST _analyze
{
  "analyzer": "standard",
  "text":     "Like X 国庆放假的"
}

返回结果:

{
    "tokens": [
        {
            "token": "like",
            "start_offset": 0,
            "end_offset": 4,
            "type": "<ALPHANUM>",
            "position": 0
        },
        {
            "token": "x",
            "start_offset": 5,
            "end_offset": 6,
            "type": "<ALPHANUM>",
            "position": 1
        },
        {
            "token": "国",
            "start_offset": 7,
            "end_offset": 8,
            "type": "<IDEOGRAPHIC>",
            "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
        }
    ]
}

区分中英文。英文按照空格切分的同时大写转小写,中文按照单个词分词。

标准分析器接收下列参数:

  • max_token_length : 最大token长度,默认255
  • stopwords : 预定义的停止词列表,如_english_ 或 包含停止词列表的数组,默认是 _none_
  • stopwords_path : 包含停止词的文件路径
PUT new_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_english_analyzer": {
          "type": "standard",       #设置分词器为standard
          "max_token_length": 5,    #设置分词最大为5
          "stopwords": "_english_"  #设置过滤词
        }
      }
    }
  }
}

Simple Analyzer

Simple 分析器当它遇到只要不是字母的字符,就将文本解析成term,而且所有的term都是小写的。

POST _analyze
{
  "analyzer": "simple",
  "text":     "Like X 国庆放假 的"
}

返回结果如下:

{
    "tokens": [
        {
            "token": "like",
            "start_offset": 0,
            "end_offset": 4,
            "type": "word",
            "position": 0
        },
        {
            "token": "x",
            "start_offset": 5,
            "end_offset": 6,
            "type": "word",
            "position": 1
        },
        {
            "token": "国庆放假",
            "start_offset": 7,
            "end_offset": 11,
            "type": "word",
            "position": 2
        },
        {
            "token": "的",
            "start_offset": 12,
            "end_offset": 13,
            "type": "word",
            "position": 3
        }
    ]
}

先按空格分词,英文大写转小写,不是英文不再分词。

Whitespace Analyzer

POST _analyze
{
  "analyzer": "whitespace",
  "text":     "Like X 国庆放假 的"
}

返回结果:

{
    "tokens": [
        {
            "token": "Like",
            "start_offset": 0,
            "end_offset": 4,
            "type": "word",
            "position": 0
        },
        {
            "token": "X",
            "start_offset": 5,
            "end_offset": 6,
            "type": "word",
            "position": 1
        },
        {
            "token": "国庆放假",
            "start_offset": 7,
            "end_offset": 11,
            "type": "word",
            "position": 2
        },
        {
            "token": "的",
            "start_offset": 12,
            "end_offset": 13,
            "type": "word",
            "position": 3
        }
    ]
}

按空格分词,英文不区分大小写,中文不再分词

 

posted @ 2022-02-13 21:56  残城碎梦  阅读(172)  评论(0编辑  收藏  举报