掌握 analyze API,一舉搞定 Elasticsearch 分詞難題

初次接觸 Elasticsearch 的同學(xué)經(jīng)常會(huì)遇到分詞相關(guān)的難題,比如如下這些場景:

  1. 為什么命名有包含搜索關(guān)鍵詞的文檔,但結(jié)果里面就沒有相關(guān)文檔呢?
  2. 我存進(jìn)去的文檔到底被分成哪些詞(term)了?
  3. 我得自定義分詞規(guī)則,但感覺好麻煩呢,無從下手

如果你遇到過類似的問題,希望本文可以解決你的疑惑。

1. 上手

讓我們從一個(gè)實(shí)例出發(fā),如下創(chuàng)建一個(gè)文檔:

PUT test/doc/1
{
  "msg":"Eating an apple a day keeps doctor away"
}

然后我們做一個(gè)查詢,我們?cè)噲D通過搜索 eat這個(gè)關(guān)鍵詞來搜索這個(gè)文檔

POST test/_search
{
  "query":{
    "match":{
      "msg":"eat"
    }
  }
}

ES的返回結(jié)果為0。這不太對(duì)啊,我們用最基本的字符串查找也應(yīng)該能匹配到上面新建的文檔才對(duì)??!

各位不要急,我們先來看看什么是分詞。

2. 分詞

搜索引擎的核心是倒排索引(這里不展開講),而倒排索引的基礎(chǔ)就是分詞。所謂分詞可以簡單理解為將一個(gè)完整的句子切割為一個(gè)個(gè)單詞的過程。在 es 中單詞對(duì)應(yīng)英文為 term。我們簡單看個(gè)例子:

image

ES 的倒排索引即是根據(jù)分詞后的單詞創(chuàng)建,即 、北京、天安門這4個(gè)單詞。這也意味著你在搜索的時(shí)候也只能搜索這4個(gè)單詞才能命中該文檔。

實(shí)際上 ES 的分詞不僅僅發(fā)生在文檔創(chuàng)建的時(shí)候,也發(fā)生在搜索的時(shí)候,如下圖所示:

image

讀時(shí)分詞發(fā)生在用戶查詢時(shí),ES 會(huì)即時(shí)地對(duì)用戶輸入的關(guān)鍵詞進(jìn)行分詞,分詞結(jié)果只存在內(nèi)存中,當(dāng)查詢結(jié)束時(shí),分詞結(jié)果也會(huì)隨即消失。而寫時(shí)分詞發(fā)生在文檔寫入時(shí),ES 會(huì)對(duì)文檔進(jìn)行分詞后,將結(jié)果存入倒排索引,該部分最終會(huì)以文件的形式存儲(chǔ)于磁盤上,不會(huì)因查詢結(jié)束或者 ES 重啟而丟失。

ES 中處理分詞的部分被稱作分詞器,英文是Analyzer,它決定了分詞的規(guī)則。ES 自帶了很多默認(rèn)的分詞器,比如Standard、 KeywordWhitespace等等,默認(rèn)是 Standard。當(dāng)我們?cè)谧x時(shí)或者寫時(shí)分詞時(shí)可以指定要使用的分詞器。

3. 寫時(shí)分詞結(jié)果

回到上手階段,我們來看下寫入的文檔最終分詞結(jié)果是什么。通過如下 api 可以查看:

POST test/_analyze
{
  "field": "msg",
  "text": "Eating an apple a day keeps doctor away"
}

其中 test為索引名,_analyze 為查看分詞結(jié)果的 endpoint,請(qǐng)求體中 field 為要查看的字段名,text為具體值。該 api 的作用就是請(qǐng)告訴我在 test 索引使用 msg 字段存儲(chǔ)一段文本時(shí),es 會(huì)如何分詞。

返回結(jié)果如下:

{
  "tokens": [
    {
      "token": "eating",
      "start_offset": 0,
      "end_offset": 6,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "an",
      "start_offset": 7,
      "end_offset": 9,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "apple",
      "start_offset": 10,
      "end_offset": 15,
      "type": "<ALPHANUM>",
      "position": 2
    },
    {
      "token": "a",
      "start_offset": 16,
      "end_offset": 17,
      "type": "<ALPHANUM>",
      "position": 3
    },
    {
      "token": "day",
      "start_offset": 18,
      "end_offset": 21,
      "type": "<ALPHANUM>",
      "position": 4
    },
    {
      "token": "keeps",
      "start_offset": 22,
      "end_offset": 27,
      "type": "<ALPHANUM>",
      "position": 5
    },
    {
      "token": "doctor",
      "start_offset": 28,
      "end_offset": 34,
      "type": "<ALPHANUM>",
      "position": 6
    },
    {
      "token": "away",
      "start_offset": 35,
      "end_offset": 39,
      "type": "<ALPHANUM>",
      "position": 7
    }
  ]
}

返回結(jié)果中的每一個(gè) token即為分詞后的每一個(gè)單詞,我們可以看到這里是沒有 eat 這個(gè)單詞的,這也解釋了在上手中我們搜索 eat 沒有結(jié)果的情況。如果你去搜索 eating ,會(huì)有結(jié)果返回。

寫時(shí)分詞器需要在 mapping 中指定,而且一經(jīng)指定就不能再修改,若要修改必須新建索引。如下所示我們新建一個(gè)名為ms_english 的字段,指定其分詞器為 english

PUT test/_mapping/doc
{
  "properties": {
    "msg_english":{
      "type":"text",
      "analyzer": "english"
    }
  }
}

4. 讀時(shí)分詞結(jié)果

由于讀時(shí)分詞器默認(rèn)與寫時(shí)分詞器默認(rèn)保持一致,拿 上手 中的例子,你搜索 msg 字段,那么讀時(shí)分詞器為 Standard ,搜索 msg_english 時(shí)分詞器則為 english。這種默認(rèn)設(shè)定也是非常容易理解的,讀寫采用一致的分詞器,才能盡最大可能保證分詞的結(jié)果是可以匹配的。

然后 ES 允許讀時(shí)分詞器單獨(dú)設(shè)置,如下所示:

POST test/_search
  {
    "query":{
      "match":{
        "msg":{
          "query": "eating",
          "analyzer": "english"
        }
      }
    }
  }

如上 analyzer 字段即可以自定義讀時(shí)分詞器,一般來講不需要特別指定讀時(shí)分詞器。

如果不單獨(dú)設(shè)置分詞器,那么讀時(shí)分詞器的驗(yàn)證方法與寫時(shí)一致;如果是自定義分詞器,那么可以使用如下的 api 來自行驗(yàn)證結(jié)果。

POST _analyze
  {
    "text":"eating",
    "analyzer":"english"
  }

返回結(jié)果如下:

{
  "tokens": [
    {
      "token": "eat",
      "start_offset": 0,
      "end_offset": 6,
      "type": "<ALPHANUM>",
      "position": 0
    }
  ]
}

由上可知 english分詞器會(huì)將 eating處理為 eat,大家可以再測試下默認(rèn)的 standard分詞器,它沒有做任何處理。

5. 解釋問題

現(xiàn)在我們?cè)賮砜聪?上手 中所遇問題的解決思路。

  1. 查看文檔寫時(shí)分詞結(jié)果
  2. 查看查詢關(guān)鍵詞的讀時(shí)分詞結(jié)果
  3. 匹對(duì)兩者是否有命中

我們簡單分析如下:

image

由上圖可以定位問題的原因了。

6. 解決需求

由于 eating只是 eat的一個(gè)變形,我們依然希望輸入 eat時(shí)可以匹配包含 eating的文檔,那么該如何解決呢?

答案很簡單,既然原因是在分詞結(jié)果不匹配,那么我們就換一個(gè)分詞器唄~ 我們可以先試下 ES 自帶的 english分詞器,如下:

# 增加字段 msg_english,與 msg 做對(duì)比
PUT test/_mapping/doc
{
  "properties": {
    "msg_english":{
      "type":"text",
      "analyzer": "english"
    }
  }
}

# 寫入相同文檔
PUT test/doc/1
{
  "msg":"Eating an apple a day keeps doctor away",
  "msg_english":"Eating an apple a day keeps doctor away"
}

# 搜索 msg_english 字段
POST test/_search
{
  "query": {
    "match": {
      "msg_english": "eat"
    }
  }
}

執(zhí)行上面的內(nèi)容,我們會(huì)發(fā)現(xiàn)結(jié)果有內(nèi)容了,原因也很簡單,如下圖所示:

image

由上圖可見 english分詞器會(huì)將 eating分詞為 eat,此時(shí)我們搜索 eat或者 eating肯定都可以匹配對(duì)應(yīng)的文檔了。至此,需求解決。

7. 深入分析

最后我們來看下為什么english分詞器可以解決我們遇到的問題。一個(gè)分詞器由三部分組成:char filter、tokenizer 和 token filter。各部分的作用我們這里就不展開了,我們來看下 standardenglish分詞器的區(qū)別。

image

從上圖可以看出,english分詞器在 Token Filter 中和 Standard不同,而發(fā)揮主要作用的就是 stemmer,感興趣的同學(xué)可以自行去看起它的作用。

8. 自定義分詞

如果我們不使用 english分詞器,自定義一個(gè)分詞器來實(shí)現(xiàn)上述需求也是完全可行的,這里不詳細(xì)講解了,只給大家講一個(gè)快速驗(yàn)證自定義分詞器效果的方法,如下:

POST _analyze
{
  "char_filter": [], 
  "tokenizer": "standard",
  "filter": [
    "stop",
    "lowercase",
    "stemmer"
  ],
  "text": "Eating an apple a day keeps doctor away"
}

通過上面的 api 你可以快速驗(yàn)證自己要定制的分詞器,當(dāng)達(dá)到自己需求后,再將這一部分配置加入索引的配置。

至此,我們?cè)倏撮_篇的三個(gè)問題,相信你已經(jīng)心里有答案了,趕緊上手去自行測試下吧!

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Solr&ElasticSearch原理及應(yīng)用 一、綜述 搜索 http://baike.baidu.com/it...
    樓外樓V閱讀 7,637評(píng)論 1 17
  • 倒排索引 正排索引:文檔id到單詞的關(guān)聯(lián)關(guān)系 倒排索引:單詞到文檔id的關(guān)聯(lián)關(guān)系 示例:對(duì)以下三個(gè)文檔去除停用詞后...
    小旋鋒的簡書閱讀 4,747評(píng)論 1 11
  • 表面的你可能就是一根草,但不為人知的另一面,卻隱藏著巨大的寶藏。所以不要忘記生長,當(dāng)你被發(fā)現(xiàn)的那一刻,驚喜與努力將...
    sunnyinkfish閱讀 136評(píng)論 0 0
  • 真相并沒有什么可怕的。 理解、接受并且知道應(yīng)該如何處理現(xiàn)實(shí)問題是至關(guān)重要的。 為什么會(huì)害怕真相,恐懼事實(shí)? 因?yàn)槭?..
    lamanda閱讀 641評(píng)論 0 0
  • 生活就是一直摸索前進(jìn),慢慢的撥開云霧,發(fā)現(xiàn)自己想要的,從前我不知道目標(biāo)是什么,突然的,就發(fā)現(xiàn)了自己是個(gè)什么樣的人,...
    LU小Y閱讀 206評(píng)論 0 0

友情鏈接更多精彩內(nèi)容