Elasticsearch中的分析器介绍

读前声明

文中一些专有名词所对应的英文名称

英文名称 中文翻译
token 分词
Inverted Index 倒排索引
Analyzer 分析器
Character Filters 字符过滤器
Tokenizer 分词器
Token Filter 分词过滤器

正式内容

如果想用 Elasticsearch创建一个好的搜索引擎,必须要了解分析器(Analyzer)的工作原理。一个好的搜索引擎能够返回用户查询相关的文档。 Elasticsearch 中的分析器负责处理用户要索引的文档,并且在查询时评估哪些文档与查询关键词更匹配。

倒排索引

由于分析器与倒排索引紧密相关,因此需要首先了解什么是倒排索引。

倒排索引是一种数据结构,用于存储分词与具有该分词的文档编号之间的映射。除了文档编号之外,倒排索引还存储该分词在文档中的位置。由于 Elasticsearch 将分词与文档编号进行映射,因此使用Elasticsearch 进行关键词查询,它可以快速获取关键词所匹配的文档编号并返回。

文档构建倒排索引

假设现在有两条文档
文档1:Elasticsearch is fast
文档2:I want to learn Elasticsearch
下图为倒排索引构建的结果:第一列为分词内容,第二列为分词出现的次数,第三列为分词所在的文档编号以及在文档中的位置。

倒排索引

分词统计后映射到文档编号并保存了其在文档中的位置。我们没有看到完整的文档是因为其经过分析器的分析,这也是本篇文章的主旨。

在倒排索引中查询

关于查询倒排索引时有一点需要注意:Elasticsearch 只会获取与查询关键词具有相同分词的文档,通过Elasticsearch的match queryterm query两类查询可以非常简单进行验证。match query会使用分析器进行分析,而term query则不会。另一篇文档有详细描述这两类查询的区别。

如果使用term query以"Elasticsearch"为关键词在上述索引中进行查询,不会得到任何结果,因为分词在倒排索引中存储的是"elasticsearch",其开头字母是小写的"e"。如果用相同的关键字使用match query查询,Elasticsearch会首先使用分析器对查询关键字进行分析,得到"elasticsearch",再到倒排索引中进行检索,因此可以返回相应结果。

Elasticsearch中的分析器(Analyzer)是什么?

当在Elasticsearch中插入一篇文档时,Elasticsearch不会对文档内容进行原样保存,而是先经过分析器分析。在分析的过程中,分析器首先对文本转换并将其拆分为分词,然后在保存到倒排索引中。
例如:插入“Let’s build an Autocomplete!”到Elasticsearch中,文本转换后将分别划分为4个分词:“let’s”, “build”, “an”和“autocomplete”。


image.png

分析器会影响搜索文本的方式,但不会影响文本本身的内容。在前面的例子中,如果我们搜索“let”,Elasticsearch 仍然会返回全文“Let's build an autocomplete!”而不仅仅是“let”单个词。

Elasticsearch中分析器的API

Elasticsearch提供了API供用户查询分析器对文本进行分析后的效果, API 可以大大简化分析器的调试过程。其中本文是在kibana的Dev Tools使用命令查看。

GET /_analyze
{
  "text": "Let’s build an Autocomplete!",
  "analyzer": "standard"
}

响应结果

{
  "tokens" : [
    {
      "token" : "let’s",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "build",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "an",
      "start_offset" : 12,
      "end_offset" : 14,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "autocomplete",
      "start_offset" : 15,
      "end_offset" : 27,
      "type" : "<ALPHANUM>",
      "position" : 3
    }
  ]
}

Elasticsearch中分析器的组成

Elasticsearch的分析器主要由三部分组成,用户可以对其自行调整

  • 字符过滤器(Character Filters)
  • 分词器(Tokenizer)
  • 分词过滤器(Token Filter)

字符过滤器(Character Filters)

分析过程中的第一个阶段是字符过滤,用于删除、添加和替换文本中的字符。
Elasticsearch 中有三个内置的字符过滤器:

  • HTML字符过滤器:去除html中的标签,如<b>, <i>, <div>, <br />等。
  • 映射字符过滤器:该滤器可将一个词映射到另一个词。例如,如果想让用户可以搜索表情符号,可以将“:)”映射到“smile”
  • 正则替换字符过滤器:将正则表达式替换为另一个词。不过使用该过滤器会减慢文档索引的速度。

分词器(Tokenizer)

经过字符过滤器过滤后,分词器会对文本进行划分,分为若干个分词。例如,先前的"Let’s build an autocomplete"文本会被划分为“let’s”, “build”, “an”和“autocomplete”4个分词,这个过程就是分词器所负责的。Elasticsearch中有大量的分词器,可以参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html
常用的分词器有:

  • 标准分词器(Standard Tokenizer):Elasticsearch 的默认分词器,按照空格和标点符号划分文本。
  • 空格分词器(Whitespace Tokenizer):对文本仅按空格进行划分。
  • Edge N-Gram Tokenizer:对于自动补全非常有用,会按照空格和字符对单词进行划分,例如:Hello划分为 -> “H”, “He”, “Hel”, “Hell”, “Hello”。

在使用分词器时需要注意,因为太多的分词器会减慢文档的插入速度。

分词过滤器(Token Filter)

分词过滤器是分析器分析过程中的第三个阶段,也是最后的阶段。此过程将根据使用的分词过滤器转换分词。在分词过滤的过程中,可以小写转换,删除停用词,并为分词添加同义词。
Elasticsearch有大量的分词过滤器,可以参考https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html
最常用的分词过滤器是小写分词过滤器,用于将分词转换为小写。

标准分析器(Standard Analyzer)

标准分析器是Elasticsearch默认的分析器,如果你在构建文档mapping的时候没有指明分析器,则会使用标准分析器。其语法可参考:https://unicode.org/reports/tr29/
标准分析器主要由以下3部分组成:

  • 标准分词器(Standard Tokenizer)
  • 小写分词过滤器(Lower Case Token Filter)
  • 停用词过滤器(默认不启动)

其主要功能有:

  • 按照空格和标点符号对文本进行分词
  • 将分词转化为小写
  • 如果启用停用词过滤器,将会移除文本中的停用词

以"Let’s learn about Analyzer!"为例,看一下标准分析器的分析结果。

GET /_analyze
{
  "text": "Let’s build an Autocomplete!",
  "analyzer": "standard"
}

响应结果

{
  "tokens" : [
    {
      "token" : "let’s",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "build",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "an",
      "start_offset" : 12,
      "end_offset" : 14,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "autocomplete",
      "start_offset" : 15,
      "end_offset" : 27,
      "type" : "<ALPHANUM>",
      "position" : 3
    }
  ]
}

可以看到标准分析器按照空格将文本拆分为若干个分词,并且还删除了标点符号“!”,因为它之后没有更多的分词。所有的分词都是小写的,因为标准分析器使用小写分词过滤器。
只使用标准分析器就可以实现一个简单的自动补全功能。如果通过使用不同的字符过滤器、分词器和分词过滤器来自定义分析器,便可以实现更高级的自动补全,产生更相关的结果。

自定义分析器

自定义分析器可以在根据需要自行定义其中的名称和组件。为了创建自定义的分析器,可以在创建索引的时候,在Elasticsearch的settings中定义。

PUT /autocomplete-custom-analyzer
{
  "settings": {
    "analysis": {
      "analyzer": {
        "custom_analyzer":{
          "type":"custom",
          "tokenizer":"whitespace",
          "char_filter":["html_strip"],
          "filter":["lowercase"]
        }
      }
    }
  }
}

上述语句,自定义了一个分析器,包括:空白分词器,html字符过滤器,小写分词过滤器。
使用"<b>Let’s build an autocomplete!</b>"文本进行测试标准分析器和自定义分析器的分析结果。

  • 标准分析器
GET /_analyze
{
  "text": "<b>Let’s build an autocomplete!</b>",
  "analyzer": "standard"
}

响应结果

{
  "tokens" : [
    {
      "token" : "b",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "let’s",
      "start_offset" : 3,
      "end_offset" : 8,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "build",
      "start_offset" : 9,
      "end_offset" : 14,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "an",
      "start_offset" : 15,
      "end_offset" : 17,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "autocomplete",
      "start_offset" : 18,
      "end_offset" : 30,
      "type" : "<ALPHANUM>",
      "position" : 4
    },
    {
      "token" : "b",
      "start_offset" : 33,
      "end_offset" : 34,
      "type" : "<ALPHANUM>",
      "position" : 5
    }
  ]
}
  • 自定义分析器
GET /autocomplete-custom-analyzer/_analyze
{
  "text": "<b>Let’s build an autocomplete!</b>",
  "analyzer": "custom_analyzer"
}

响应结果

{
  "tokens" : [
    {
      "token" : "let’s",
      "start_offset" : 3,
      "end_offset" : 8,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "build",
      "start_offset" : 9,
      "end_offset" : 14,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "an",
      "start_offset" : 15,
      "end_offset" : 17,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "autocomplete!",
      "start_offset" : 18,
      "end_offset" : 35,
      "type" : "word",
      "position" : 3
    }
  ]
}

可以发现以下几点不同:

  • 标准分析器有两个b分词,而自定义分析器没有,这是因为自定义分析器中的html字符过滤器会将html移除。
  • 标准分析器按照空格或者具体的字符如<>对文本进行划分,而自定义分析器仅按照空格对文本进行划分。
  • 标准分析器会移除特殊字符,而自定义分析器不会。如标准分析器后的分词为autocomplete,而自定义分析器是autocomplete!

最后一步是设置索引的mapping,不同的字段使用不同的分析器:standard-text字段使用标准分析器,cust_analyzer-text字段使用自定义分析器。

PUT /autocomplete-custom-analyzer/_mapping
{
    "properties": {
        "standard-text": {
            "type": "text",
            "analyzer": "standard"
        },
        "cust_analyzer-text": {
            "type": "text",
            "analyzer": "custom_analyzer"
        }
    }
}

添加两条文档:

POST /autocomplete-custom-analyzer/_doc/1
{
  "standard-text":"<b>Let's build an autocomplete!</b>"
}

POST /autocomplete-custom-analyzer/_doc/2
{
  "cust_analyzer-text":"<b>Let's build an autocomplete!</b>"
}

bool查询

GET /autocomplete-custom-analyzer/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "standard-text": {
              "value": "b"
            }
          }
        },
        {
          "term": {
            "cust_analyzer-text": {
              "value": "b"
            }
          }
        }
      ]
    }
  }
}

响应结果

{
  "took" : 35,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.25069216,
    "hits" : [
      {
        "_index" : "autocomplete-custom-analyzer",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.25069216,
        "_source" : {
          "standard-text" : "<b>Let's build an autocomplete!</b>"
        }
      }
    ]
  }
}

结果返回使用标准分析器索引的文档,因为在标准分析器中没有使用 html字符过滤器去除 html 标签,而在自定义分析器过滤了。

尝试另一条查询语句,使用autocomplete!关键字查询

GET /autocomplete-custom-analyzer/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "standard-text": {
              "value": "autocomplete!"
            }
          }
        },
        {
          "term": {
            "cust_analyzer-text": {
              "value": "autocomplete!"
            }
          }
        }
      ]
    }
  }
}

响应结果

{
  "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" : "autocomplete-custom-analyzer",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.2876821,
        "_source" : {
          "cust_analyzer-text" : "<b>Let's build an autocomplete!</b>"
        }
      }
    ]
  }
}

现在,结果返回使用自定义分析器索引的文档。由于标准分析器通过空格和标点符号对文档进行分词,因此它删除了!符号。 自定义分析器仅按空格划分文档,所以该标点符号没有被删除。

结论

如果想要创建一个好的搜索引擎,分析器Analyzer是必须要学习的重要组件。它是控制在用户查询单词时返回哪些文档的第一步。

参考

https://codecurated.com/blog/introduction-to-analyzer-in-elasticsearch/#a-bit-about-inverted-index

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容