Elasticsearch當中的分析器-Analyzer

前言

相信很多朋友剛開始在使用Elasticsearch的時候,一定都會遇到一個問題: 我的檔案內容清清楚楚的寫在那, 怎麼就是搜尋不到? 其中很大的可能就是分析器沒有正確配置唷!


舉個例子

搜尋英文進行式單詞

首先放入一筆資料, 內容是 "Set the shape to semi-transparent by calling set_trans(5)"

root@ubuntu-87:~# curl -XPOST http://localhost:9200/demo/demotype/1 -H 'Content-Type:application/json' -d '
{
  "content": "Set the shape to semi-transparent by calling set_trans(5)"
}' | jq .

{
  "_index": "demo",
  "_type": "demotype",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}

然後我們試著搜尋 call; 結果是搜尋不到這筆資料.

root@ubuntu-87:~# curl -s -XPOST http://localhost:9200/demo/demotype/_search  -H 'Content-Type:application/json' -d '
{
  "query" : {
    "match" : {
      "content" : "call"
    }
  }
}' | jq .

{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 0,
    "max_score": null,
    "hits": []
  }
}

原因

在Elasticsearch可以搜尋文檔之前, 他必須先將字詞內容做拆分(分詞)和加工, 然後透過這些拆分好的詞建構倒排索引, 之後使用者才能在透過關鍵詞搜尋到我們想要的文檔.

而文檔內容要如何分詞或是加工, 依靠的就是分析器的配置規則囉!

也就是說,Elasticsearch認定的分詞是 calling 不是call, 所以搜尋不到.


分析器介紹

先附上文檔
英文: Elasticsearch: The Definitive Guide - Analysis and Analyzers
中文: Elasticsearch: 权威指南 - 分析与分析器

分析器是由: 字符過濾器(Character filters), 分詞器(Tokenizer), Token過濾器(Token filters) 組成的; 他們的工作流程如下

  1. 字符過濾器(Character filters): 預處理
    首先,字符串按順序通過每個 字符過濾器 。他們的任務是在分詞前整理字符串。一個字符過濾器可以用來去掉HTML,或者將 & 轉化成 and

  2. 分詞器(Tokenizer): 主要分詞工作
    其次,字符串被 分詞器 分為單個的詞條。一個簡單的分詞器遇到空格和標點的時候,可能會將文本拆分成詞條。

  3. Token過濾器(Token filters): 後續加工
    最後,詞條按順序通過每個 token 過濾器 。這個過程可能會改變詞條(例如,小寫化 Quick ),刪除詞條(例如, 像 aandthe 等無用詞),或者增加詞條(例如,像 jump 和 leap 這種同義詞)。

內置分析器效果預覽

Elasticsearch 內置了幾種分析器, 透過套用不同的分析器就可以讓這句文檔產生不同的索引效果

  • 標準分析器 (Standard analyzer)
    標準分析器是Elasticsearch默認使用的分析器。它是分析各種語言文本最常用的選擇。它根據 Unicode 聯盟 定義的 單詞邊界 劃分文本。刪除絕大部分標點。最後,將詞條小寫。他會產生
    set, the, shape, to, semi, transparent, by, calling, set_trans, 5

  • 簡單分析器 (Simple analyzer)
    簡單分析器在任何不是字母的地方分隔文本,將詞條小寫。它會產生
    set, the, shape, to, semi, transparent, by, calling, set, trans

  • 空格分析器 (Whitespace analyzer)
    空格分析器在空格的地方劃分文本。它會產生
    Set, the, shape, to, semi-transparent, by, calling, set_trans(5)

  • 語言分析器 (Language analyzers)
    特定語言分析器可用於 很多語言。它們可以考慮指定語言的特點。例如, 英語 分析器附帶了一組英語無用詞(常用單詞,例如 and 或者 the ,它們對相關性沒有多少影響),它們會被刪除。 由於理解英語語法的規則,這個分詞器可以提取英語單詞的 詞幹 。

英語 分詞器會產生下面的詞條:

set, shape, semi, transpar, call, set_tran, 5
註意看 transparent, callingset_trans 已經變為詞根格式。

看到這邊大家應該了解到如果想要搜尋英文詞根的話; 必須套用語言分析器~


配置分析器

分析器的配置放在索引的mapping之下, 而更改過的mapping無法影響已存在的文檔; 所以我們另外配置一個demo1來做示範

  1. 新增索引: curl -XPUT http://localhost:9200/demo1
  2. 配置映射:
curl -XPOST http://localhost:9200/demo1/demotype/_mapping -H 'Content-Type:application/json' -d'
{
  "properties": {
    "content": {
      "type": "text",
      "analyzer": "english"
    }
  }
}'
  1. 新增文檔: curl -XPOST http://localhost:9200/demo1/demotype/1 -H 'Content-Type:application/json' -d '{"content":"Set the shape to semi-transparent by calling set_trans(5)"}'

  2. 嘗試搜尋:

root@ubuntu-87:~# curl -s -XPOST http://localhost:9200/demo1/demotype/_search  -H 'Content-Type:application/json' -d '
{
  "query" : {
    "match" : {
      "content" : "call"
    }
  }
}' | jq .

{
  "took": 19,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "demo1",
        "_type": "demotype",
        "_id": "1",
        "_score": 0.2876821,
        "_source": {
          "content": "Set the shape to semi-transparent by calling set_trans(5)"
        }
      }
    ]
  }
}

查看分詞效果

僅管我們進行了這些配置, 但是只能透過搜尋來測試有沒有成功, 這樣子還是比較模糊的.
所以 Elasticsearch 提供了兩個 api 讓我們可以直接查看分詞效果: _termvectors_analyze, 而不管是哪一個都提供了以下資訊

  • 剛檔案的分詞組成
  • start_offset, end_offset: 字詞出現的位置, 用於高亮搜索
  • position: 分詞出現的順序
  • type: 詞的類別

分詞效果-預設狀態

使用_analyze查看

root@ubuntu-87:~# curl 'http://localhost:9200/demo/_analyze?pretty=true' -H 'Content-Type: application/json' -d '{ "field": "content", "text":"Set the shape to semi-transparent by calling set_trans(5)"}'

{
  "tokens" : [
    {
      "token" : "set",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "the",
      "start_offset" : 4,
      "end_offset" : 7,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "shape",
      "start_offset" : 8,
      "end_offset" : 13,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "to",
      "start_offset" : 14,
      "end_offset" : 16,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "semi",
      "start_offset" : 17,
      "end_offset" : 21,
      "type" : "<ALPHANUM>",
      "position" : 4
    },
    {
      "token" : "transparent",
      "start_offset" : 22,
      "end_offset" : 33,
      "type" : "<ALPHANUM>",
      "position" : 5
    },
    {
      "token" : "by",
      "start_offset" : 34,
      "end_offset" : 36,
      "type" : "<ALPHANUM>",
      "position" : 6
    },
    {
      "token" : "calling",
      "start_offset" : 37,
      "end_offset" : 44,
      "type" : "<ALPHANUM>",
      "position" : 7
    },
    {
      "token" : "set_trans",
      "start_offset" : 45,
      "end_offset" : 54,
      "type" : "<ALPHANUM>",
      "position" : 8
    },
    {
      "token" : "5",
      "start_offset" : 55,
      "end_offset" : 56,
      "type" : "<NUM>",
      "position" : 9
    }
  ]
}

使用_termvectors查看

root@ubuntu-87:~# curl -H "Content-Type:application/json"  'http://localhost:9200/demo/demotype/1/_termvectors?pretty=true' -d '{"fie
lds" : ["content"]}'

{
  "_index" : "demo",
  "_type" : "demotype",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "took" : 0,
  "term_vectors" : {
    "content" : {
      "field_statistics" : {
        "sum_doc_freq" : 10,
        "doc_count" : 1,
        "sum_ttf" : 10
      },
      "terms" : {
        "5" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 9,
              "start_offset" : 55,
              "end_offset" : 56
            }
          ]
        },
        "by" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 6,
              "start_offset" : 34,
              "end_offset" : 36
            }
          ]
        },
        "calling" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 7,
              "start_offset" : 37,
              "end_offset" : 44
            }
          ]
        },
        "semi" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 4,
              "start_offset" : 17,
              "end_offset" : 21
            }
          ]
        },
        "set" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 0,
              "start_offset" : 0,
              "end_offset" : 3
            }
          ]
        },
        "set_trans" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 8,
              "start_offset" : 45,
              "end_offset" : 54
            }
          ]
        },
        "shape" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 2,
              "start_offset" : 8,
              "end_offset" : 13
            }
          ]
        },
        "the" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 1,
              "start_offset" : 4,
              "end_offset" : 7
            }
          ]
        },
        "to" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 3,
              "start_offset" : 14,
              "end_offset" : 16
            }
          ]
        },
        "transparent" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 5,
              "start_offset" : 22,
              "end_offset" : 33
            }
          ]
        }
      }
    }
  }
}

分詞效果-經過配置後

使用_analyze查看

root@ubuntu-87:~# curl 'http://localhost:9200/demo1/_analyze?pretty=true' -H 'Content-Type: application/json' -d '{ "field": "content
", "text":"Set the shape to semi-transparent by calling set_trans(5)"}'
{
  "tokens" : [
    {
      "token" : "set",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "shape",
      "start_offset" : 8,
      "end_offset" : 13,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "semi",
      "start_offset" : 17,
      "end_offset" : 21,
      "type" : "<ALPHANUM>",
      "position" : 4
    },
    {
      "token" : "transpar",
      "start_offset" : 22,
      "end_offset" : 33,
      "type" : "<ALPHANUM>",
      "position" : 5
    },
    {
      "token" : "call",
      "start_offset" : 37,
      "end_offset" : 44,
      "type" : "<ALPHANUM>",
      "position" : 7
    },
    {
      "token" : "set_tran",
      "start_offset" : 45,
      "end_offset" : 54,
      "type" : "<ALPHANUM>",
      "position" : 8
    },
    {
      "token" : "5",
      "start_offset" : 55,
      "end_offset" : 56,
      "type" : "<NUM>",
      "position" : 9
    }
  ]
}

使用_termvectors查看

root@ubuntu-87:~# curl -H "Content-Type:application/json"  'http://localhost:9200/demo1/demotype/1/_termvectors?pretty=true' -d '{"fi
elds" : ["content"]}'
{
  "_index" : "demo1",
  "_type" : "demotype",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "took" : 1,
  "term_vectors" : {
    "content" : {
      "field_statistics" : {
        "sum_doc_freq" : 7,
        "doc_count" : 1,
        "sum_ttf" : 7
      },
      "terms" : {
        "5" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 9,
              "start_offset" : 55,
              "end_offset" : 56
            }
          ]
        },
        "call" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 7,
              "start_offset" : 37,
              "end_offset" : 44
            }
          ]
        },
        "semi" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 4,
              "start_offset" : 17,
              "end_offset" : 21
            }
          ]
        },
        "set" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 0,
              "start_offset" : 0,
              "end_offset" : 3
            }
          ]
        },
        "set_tran" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 8,
              "start_offset" : 45,
              "end_offset" : 54
            }
          ]
        },
        "shape" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 2,
              "start_offset" : 8,
              "end_offset" : 13
            }
          ]
        },
        "transpar" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 5,
              "start_offset" : 22,
              "end_offset" : 33
            }
          ]
        }
      }
    }
  }
}

尾聲

這樣基礎的分析器概念就說完囉, 之後會在新增中文分析器的利器ik分析器