Elasticsearch: analyzer

Elastic 专栏收录该内容
494 篇文章 87 订阅

如果大家之前看过我写的文章 “开始使用Elasticsearch (3)”,在文章的最后部分写了有关于 analyzer 的有关介绍。在这里,我必须指出的是,analyzer 只作用于text 类型的字段,而对于 keyword 类型的字段,它将不被分析和分词。keyword 字段被用于精确匹配及聚合。

在今天的文章中,我们来进一步了解 analyzer。 analyzer 执行将输入字符流分解为token的过程,它一般发生在两个场合:

  • 在 indexing 的时候,也即在建立索引的时候
  • 在 searching 的时候,也即在搜索时,分析需要搜索的词语

什么是 analysis?

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

  • Character filtering (字符过滤器): 使用字符过滤器转换字符

  •  Breaking text into tokens (把文字转化为标记): 将文本分成一组一个或多个标记

  • Token filtering:使用标记过滤器转换每个标记

  • Token indexing:把这些标记存于索引中

接下来我们将更详细地讨论每个步骤,但首先让我们看一下图表中总结的整个过程。 图5.1显示了 “share your experience with NoSql & big data technologies" 为分析的标记:share, your, experience, with, nosql, big, datatools 及 technologies

上面所展示的是一个由 character 过滤器,标准的 tokenizer 及 Token filter 组成的定制 analyzer。上面的这个图非常好,它很简洁地描述一个 analyzer 的基本组成部分,以及每个部分所需要表述的东西。

经过 analyzer 处理过后的文档最终以 Inverted Index 的形式保存于 Elasticsearch 中:

每当一个文档被 ingest 节点纳入,它需要经历如下的步骤,才能最终把文档写入到 Elasticsearch的 数据库中:

上面中间的那部分就叫做 analyzer,即分析器。它有三个部分组成:Char Filters, Tokenizer 及 Token Filter。它们的作用分别如下:

  • Char Filter: 字符过滤器的工作是执行清除任务,例如剥离 HTML 标记,还有上面的把 “&” 转换为 “and” 字符串
  • Tokenizer: 下一步是将文本拆分为称为标记的术语。 这是由 tokenizer 完成的。 可以基于任何规则(例如空格)来完成拆分。 有关 tokenizer 的更多详细信息,请访问以下URL:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html
  • Token filter: 一旦创建了 token,它们就会被传递给 token filter,这些过滤器会对 token 进行规范化。 Token filter 可以更改 token,删除术语或向 token 添加术语。

                                       

一个 analyzer 有且只有一个 tokenizer,有0个或一个以上的 char filter 及 token filter。

Elasticsearch 已经提供了比较丰富的开箱即用 analyzer。我们可以自己创建自己的 token analyzer,甚至可以利用已经有的 char filter,tokenizer 及 token filter 来重新组合成一个新的 analyzer,并可以对文档中的每一个字段分别定义自己的 analyzer。如果大家对 analyzer 比较感兴趣的话,请参阅我们的网址 https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html

在默认的情况下,standard analyzer 是 Elasticsearch 的缺省分析器:

  • 没有 Char Filter
  • 使用 standard tokonizer
  • 把字符串变为小写,同时有选择地删除一些 stop words 等。默认的情况下 stop words 为 _none_,也即不过滤任何 stop words。

下图说明了不同的分析器如何拆分 token 以及有多少 token 它们为相同的文本流生成的标记:

在这里,我想指出的是针对英文或其它西方文字 stemming filter。它的意思是找出相应词的词源:

在上面 Tunning 的词源是 tune,而 Apps 的词源是 app。如下的 english  analyzer 调用将使用 stemmer 过虑器从而返回这些词的词源:

GET _analyze
{
  "analyzer": "english",
  "text": "Running Apps in a Phone"
}

将返回如下的结果:

{
  "tokens" : [
    {
      "token" : "run",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "app",
      "start_offset" : 8,
      "end_offset" : 12,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "phone",
      "start_offset" : 18,
      "end_offset" : 23,
      "type" : "<ALPHANUM>",
      "position" : 4
    }
  ]
}

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

  • 0个或1个以上的 character filter
  • 1个 tokenizer
  • 0个或1个以上的 token filter

Analyze API

GET /_analyze
POST /_analyze
GET /<index>/_analyze
POST /<index>/_analyze

使用 _analyze API 来测试 analyzer 如何解析我们的字符串的,比如:

GET /_analyze
{
  "analyzer": "standard",
  "text": "Quick Brown Foxes!"
}

返回结果:


  "tokens" : [
    {
      "token" : "quick",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "brown",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "foxes",
      "start_offset" : 12,
      "end_offset" : 17,
      "type" : "<ALPHANUM>",
      "position" : 2
    }
  ]
}

在这里我们使用了 standard 分析器,它把我们的字符串分解为三个 token,并显示它们分别的位置信息。

Multi-field 字符字段

我们可以针对这个使用多个不同的 anaylzer 来提高我们的搜索:使用不同的分析器来分析同样的一个字符串,用不同的方式。我们可以使用现有的分析器俩设置一个定制的分析器。比如我们定义如下的一个 mapping:

PUT multifield
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "standard", 
        "fields": {
          "english": {
            "type": "text",
            "analyzer": "english"
          }
        }
      }
    }
  }
}

在这里我们定义了一个叫做 multi-field 的索引,我们可以对这个索引进行分析。我们对整个 field 定义了一个 standard 分析器,同时为叫做 english 的字段定义了一个 english 的分析器,这样有利于我们删除一些 stop words 及运用一些同根词。我们首先来为 multi-field 来建立一个文档:

PUT multifield/_doc/1
{
  "content": "We are excited to introduce the world to X-Pack"
}

那么我们可以通过如下的方法来进行搜索:

GET /multifield/_search
{
  "query": {
    "match": {
      "content": "the"
    }
  }
}

我们可以看到搜索的结果:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "multifield",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "content" : "We are excited to introduce the world to X-Pack"
        }
      }
    ]
  }
}

我们可以看到搜寻的结果,但是如果我们使用如下的方法:

GET /multifield/_search
{
  "query": {
    "match": {
      "content.english": "the"
    }
  }
}

我们啥也看不到,这是因为 “the” 在 english analyzer 里 “the” 被认为是 stop word,而被忽略。

如何定义一个定制的分析器

在这里我们主要运用现有的 plugin 来完成定制的分析器。对于需要开发自己的 plugin 的需求,不在这篇文章的范围。

假如我们有一下的一个句子:

GET _analyze
{
  "text": "I am so excited to go to the x-game",
  "analyzer": "standard"
}

我们可以看到这样的结果:

    {
      "token" : "x",
      "start_offset" : 29,
      "end_offset" : 30,
      "type" : "<ALPHANUM>",
      "position" : 8
    },
    {
      "token" : "game",
      "start_offset" : 31,
      "end_offset" : 37,
      "type" : "<ALPHANUM>",
      "position" : 9
    }

x-game 在这里被分为两个 token:x 及 game。如果我们想把 x-game 当做一个该怎么办呢?我们可以通过设置特有的 mapping 来实现,比如我们有一个叫做 blog 的索引

PUT blogs
{
  "settings": {
    "analysis": {
      "char_filter": {
        "xgame_filter": {
          "type": "mapping",
          "mappings": [
            "X-Game => XGame"
          ]
        }
      },
      "analyzer": {
        "my_content_analyzer": {
          "type": "custom",
          "char_filter": [
            "xgame_filter"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "my_content_analyzer"
      }
    }
  }    
}

大家请注意在 settings 里的 “analysis” 部分,我们定义了一个称之为 xgame_filter 的 char_filter,它可以帮我们把 “x-game” 转化为 “XGame”。紧接着,我们利用 xgame_filter 定义了一个叫做 “my_content_analyzer”。它是一个定制的类型。我们定义它的char_filter, tokenizer 及 filter。现在我们可以利用我们刚才定义 my_content_analyzer 来分析我们的字符串。我们在 mappings 里可以看到:

  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "my_content_analyzer"
      }
    }
  }    

在这里,我们使用了刚才在 analysis 里定义的 my_content_analyzer 分析器。我们可以通过如下的方法来测试它是否工作:

POST blogs/_analyze
{
  "text": "I am so excited to go to the X-Game",
  "analyzer": "my_content_analyzer"
}

我们可以看到如下的结果:

{
  "tokens" : [
    {
      "token" : "i",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "am",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "so",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "excited",
      "start_offset" : 8,
      "end_offset" : 15,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "to",
      "start_offset" : 16,
      "end_offset" : 18,
      "type" : "<ALPHANUM>",
      "position" : 4
    },
    {
      "token" : "go",
      "start_offset" : 19,
      "end_offset" : 21,
      "type" : "<ALPHANUM>",
      "position" : 5
    },
    {
      "token" : "to",
      "start_offset" : 22,
      "end_offset" : 24,
      "type" : "<ALPHANUM>",
      "position" : 6
    },
    {
      "token" : "the",
      "start_offset" : 25,
      "end_offset" : 28,
      "type" : "<ALPHANUM>",
      "position" : 7
    },
    {
      "token" : "xgame",
      "start_offset" : 29,
      "end_offset" : 35,
      "type" : "<ALPHANUM>",
      "position" : 8
    }
  ]
}

在这里,我们可以看到 “xgame” 这个 token。

从上面的返回的结果来看,我们还是可以看到 “the”,“to” 这样的 token。如果我们想去掉这些 token 的话,我们可以做做如下的设置:

DELETE blogs

PUT blogs
{
  "settings": {
    "analysis": {
      "char_filter": {
        "xschool_filter": {
          "type": "mapping",
          "mappings": [
            "X-Game => XGame"
          ]
        }
      },
      "analyzer": {
        "my_content_analyzer": {
          "type": "custom",
          "char_filter": [
            "xschool_filter"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "my_stop"
          ]
        }
      },
      "filter": {
        "my_stop": {
          "type": "stop",
          "stopwords": ["so", "to", "the"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "my_content_analyzer"
      }
    }
  }
}

在这里,我们重新加入了一个叫做 my_stop 的过滤器:

      "filter": {
        "my_stop": {
          "type": "stop",
          "stopwords": ["so", "to", "the"]
        }
      }

我们在我们自己定制的分析器中也加入了 my_stop。重新运行我们的分析:

POST blogs/_analyze
{
  "text": "I am so excited to go to the X-Game",
  "analyzer": "my_content_analyzer"
}

在上面我们把 so, to 及 the 作为 stop words 去掉了。重新运行我们的分析:

POST blogs/_analyze
{
  "text": "I am so excited to go to the X-Game",
  "analyzer": "my_content_analyzer"
}

显示的结果为:

{
  "tokens" : [
    {
      "token" : "i",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "am",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "excited",
      "start_offset" : 8,
      "end_offset" : 15,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "go",
      "start_offset" : 19,
      "end_offset" : 21,
      "type" : "<ALPHANUM>",
      "position" : 5
    },
    {
      "token" : "xgame",
      "start_offset" : 29,
      "end_offset" : 35,
      "type" : "<ALPHANUM>",
      "position" : 8
    }
  ]
}

我们可以看到 so, the 及 to 都被过滤掉了。

Filters 的顺序也很重要

我们来试一下下面的一个例子:

GET _analyze
{
  "tokenizer": "whitespace",
  "filter": [
    "lowercase",
    "stop"
  ],
  "text": "To Be Or Not To Be"
}

在这里我们先进行 lowercase 的过滤器,先变成小写字母,再进行 stop 过滤器,那么返回的结果是[],也即没有。

相反,如果我们使用如下的顺序:

GET _analyze
{
  "tokenizer": "whitespace",
  "filter": [
    "stop",
    "lowercase"
  ],
  "text": "To Be Or Not To Be"
}

这里先进行 stop 过滤器,因为这里的词有些是大写字母,所以不被认为是 stop 词,那么没有被过滤掉。之后进行 lowercase,显示的结果是 to, be, or, not, to, be 这些 token。

search_analyzer

也许大家已经看出来了,每当一个文档在被录入到 Elasticsearch中 时,需要一个叫做 index 的过程。在 index 的过程中,它会为该字符串进行分词,并最终形成一个一个的 token,并存于数据库。但是,每当我们搜索一个字符串时,在搜索时,我们同样也要对该字符串进行分词,也会建立token。当然这些token不会被存放于数据库中。

比如:

GET /chinese/_search
{
  "query": {
    "match": {
      "content": "Happy a birthday"
    }
  }
}

对于这个搜索来说,我们在默认的情况下,会把 "Happy a birthday" 使用同样的 analyzer 进行分词。如果我们的 analyzer 里含有stop 过滤器,它极有可能把字母 “a” 过滤掉,那么直剩下 “happy” 及 “birthday” 这两个词,而 “a” 将不进入搜索之中。

在实际的使用中,我们也可以通过如下的方法对搜索进行制定具体的 search_analyzer。

PUT blogs
{
  "settings": {
    "analysis": {
      "char_filter": {
        "xschool_filter": {
          "type": "mapping",
          "mappings": [
            "X-School => XSchool"
          ]
        }
      },
      "analyzer": {
        "my_content_analyzer": {
          "type": "custom",
          "char_filter": [
            "xschool_filter"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "my_stop"
          ]
        }
      },
      "filter": {
        "my_stop": {
          "type": "stop",
          "stopwords": ["so", "to", "the"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "my_content_analyzer",
        "search_analyzer": "standard"
      }
    }
  }
}

在上面,我们可以看到,我们分别定义了不用的 analyzer:在录入文档时,我们使用了 my_content_analyzer 分析器,而在搜索时,我们使用了 standard 分析器。

内置分析器参考

Elasticsearch 附带了广泛的内置分析器,无需进一步配置即可用于任何索引:

Standard Analyzer
标准分析器按照 Unicode 文本分割算法的定义,将文本划分为单词边界上的术语。它删除了大多数标点符号、小写术语,并支持删除停用词。

Simple Analyzer
只要遇到一个不是字母的字符,简单的分析器就会将文本分成术语。它小写所有术语。

Whitespace Analyzer
每当遇到任何空白字符时,空白分析器都会将文本划分为术语。它不使用小写术语。

Stop Analyzer
停止分析器就像简单的分析器,但也支持去除停用词。

Keyword Analyzer
关键字分析器是一个 “noop” 分析器,它接受给定的任何文本并输出与单个术语完全相同的文本。

Pattern Analyzer
模式分析器使用正则表达式将文本拆分为术语。它支持小写和停用词。

Language Analyzers
Elasticsearch 提供了许多特定于语言的分析器,如英语或法语。

Fingerprint Analyzer
指纹分析仪是一种专业分析仪,可创建可用于重复检测的指纹。

如果大家想更多了解如何对中文进行分词,请参阅我的另外两篇文章 “Elasticsearch:IK中文分词器” 和 “Elasticsearch:Pinyin 分词器”。

参考:

【1】https://www.elastic.co/guide/en/elasticsearch/guide/current/_controlling_analysis.html

  • 6
    点赞
  • 4
    评论
  • 9
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值