《ElasticSearch6.x實戰教程》之分詞

OKevin發表於2019-07-20

第四章-分詞

下雨天留客天留我不留

本打算先介紹“簡單搜尋”,對ES的搜尋有一個直觀的感受。但在寫的過程中發現分詞無論如何都繞不過去。term查詢,match查詢都與分詞息息相關,索性先介紹分詞。

ES作為一個開源的搜尋引擎,其核心自然在於搜尋,而搜尋不同於我們在MySQL中的select查詢語句,無論我們在百度搜尋一個關鍵字,或者在京東搜尋一個商品時,常常無法很準確的給出一個關鍵字,例如我們在百度希望搜尋“Java教程”,我們希望結果是“Java教程”、“Java”、“Java基礎教程”,甚至是“教程Java”。MySQL雖然能滿足前三種查詢結果,但卻無法滿足最後一種搜尋結果。

雖然我們很難做到對於百度或者京東的搜尋(這甚至需要了解Lucene和搜尋的底層原理),但我們能借助ES做出一款不錯的搜尋產品。

ES的搜尋中,分詞是非常重要的概念。掌握分詞原理,對待一個不甚滿意的搜尋結果我們能定位是哪裡出了問題,從而做出相應的調整。

ES中,只對字串進行分詞,在ElasticSearch2.x版本中,字串型別只有string,ElasticSearch5.x版本後字串型別分為了textkeyword型別,需要明確的分詞只在text型別。

ES的預設分詞器是standard,對於英文搜尋它沒有問題,但對於中文搜尋它會將所有的中文字串挨個拆分,也就是它會將“中國”拆分為“中”和“國”兩個單詞,這帶來的問題會是搜尋關鍵字為“中國”時,將不會有任何結果,ES會將搜尋欄位進行拆分後搜尋。當然,你可以指定讓搜尋的欄位不進行分詞,例如設定為keyword欄位。

分詞體驗

前面說到ES的預設分詞器是standard,可直接通過API指定分詞器以及字串檢視分詞結果。

使用standard進行英文分詞:

POST http://localhost:9200/_analyze
{
    "analyzer":"standard",
    "text":"hello world" 
}

ES響應:

{
    "tokens": [
        {
            "token": "hello",
            "start_offset": 0,
            "end_offset": 5,
            "type": "<ALPHANUM>",
            "position": 0
        },
        {
            "token": "world",
            "start_offset": 6,
            "end_offset": 11,
            "type": "<ALPHANUM>",
            "position": 1
        }
    ]
}

如果我們對“helloword”進行分詞,結果將只有“helloword”一個詞,standsard對英文按照空格進行分詞。

使用standard進行中文分詞:

POST http://localhost:9200/_analyze
{
    "analyzer":"standard",
    "text":"學生" 
}

ES響應:

{
    "tokens": [
        {
            "token": "學",
            "start_offset": 0,
            "end_offset": 1,
            "type": "<IDEOGRAPHIC>",
            "position": 0
        },
        {
            "token": "生",
            "start_offset": 1,
            "end_offset": 2,
            "type": "<IDEOGRAPHIC>",
            "position": 1
        }
    ]
}

“學生”顯然應該是一個詞,不應該被拆分。也就是說如果字串中是中文,預設的standard不符合我們的需求。幸運地是, ES支援第三方分詞外掛。在ES中的中文分詞外掛使用最為廣泛的是ik外掛。

ik外掛

既然是外掛,就需要安裝。注意,版本5.0.0起,ik外掛已經不包含名為ik的分詞器,只含ik_smartik_max_word,事實上後兩者使用得也最多。

ik外掛安裝

ik下載地址(直接下載編譯好了的zip檔案,需要和ES版本一致):https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v6.3.2。ik歷史版本下載頁面:https://github.com/medcl/elasticsearch-analysis-ik/releases

下載完成後解壓elasticsearch-analysis-ik-6.3.2.zip將解壓後的資料夾直接放入ES安裝目錄下的plugins資料夾中,重啟ES。

使用ik外掛的ik_smart分詞器:

POST http://localhost:9200/_analyze
{
  "analyzer":"ik_smart",
  "text":"學生"
}

ES響應:

{
    "tokens": [
        {
            "token": "學生",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}

這才符合我們的預期。那麼ik外掛中的ik_smartik_max_word有什麼區別呢?簡單來講,ik_smart會按照關鍵字的最粗粒度進行分詞,比如搜尋“北京大學”時,我們知道“北京大學”是一個特定的詞彙,它並不是指“北京的大學”,我們不希望搜尋出“四川大學”,“重慶大學”等其他學校,此時“北京大學”不會被分詞。而ik_max_word則會按照最細粒度進行分詞,同樣搜尋“北京大學”時,我們也知道“北京”和“大學”都是一個詞彙,所以它將會被分詞為“北京大學”,“北京大”,“北京”,“大學”,顯然如果搜尋出現後三者相關結果,這會給我們帶來更多無用的資訊。

所以我們在進行搜尋時,常常指定ik_smart為分詞器。

有時候一個詞並不在ik外掛的詞庫中,例如很多網路用語等。我們希望搜尋“小米手機”的時候,只出現“小米的手機”而不會出現“華為手機”、“OPPO手機”,但“小米手機”並不在ik詞庫中,此時我們可以將“小米手機”新增到ik外掛的自定義詞庫中。

“小米手機”使用ik_smart的分詞結果:

{
    "tokens": [
        {
            "token": "小米",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "手機",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 1
        }
    ]
}

進入ik外掛安裝目錄elasticsearch-5.6.0/plugins/elasticsearch/config,建立名為custom.dic的自定義詞庫,向檔案中新增“小米手機”並儲存。仍然是此目錄,修改IKAnalyzer.cfg.xml檔案,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 擴充套件配置</comment>
        <!--使用者可以在這裡配置自己的擴充套件字典 -->
        <entry key="ext_dict">custom.dic</entry>
         <!--使用者可以在這裡配置自己的擴充套件停止詞字典-->
        <entry key="ext_stopwords"></entry>
        <!--使用者可以在這裡配置遠端擴充套件字典 -->
        <!-- <entry key="remote_ext_dict">words_location</entry> -->
        <!--使用者可以在這裡配置遠端擴充套件停止詞字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

重啟ES後,再次通過ik_smart對“小米手機”進行分詞,發現“小米手機”不再被分詞。

建立對映指定分詞器

在建立對映時,我們可以指定欄位採用哪種分詞器,避免我們在每次搜尋時都指定。

  1. 建立word索引 PUT http://localhost:9200/word

  2. 建立analyzer_demo型別已經定義對映Mapping

    PUT http://localhost:9200/word/analyzer_demo/_mapping
    {
     "properties":{
         "name":{
             "type":"text",
          "analyzer":"ik_smart"
         }
     }
    }
  3. 檢視word索引結構 GET http://localhost:9200/word

    ES響應:

    {
        "word": {
            "aliases": {},
            "mappings": {
                "analyzer_demo": {
                    "properties": {
                        "name": {
                            "type": "text",
                            "analyzer": "ik_smart"
                        }
                    }
                }
            },
            "settings": {
                "index": {
                    "creation_date": "1561304920088",
                    "number_of_shards": "5",
                    "number_of_replicas": "1",
                    "uuid": "A2YO9GpzRrGAIm2Q6rCoWA",
                    "version": {
                        "created": "5060099"
                    },
                    "provided_name": "word"
                }
            }
        }
    }

可以看到ES在對name欄位進行分詞時會採用ik_smart分詞器。

關注公眾號:CoderBuff,回覆“es”獲取《ElasticSearch6.x實戰教程》完整版PDF,回覆“抽獎”參與《從Lucene到Elasticsearch:全文檢索實戰》圖書抽獎活動(7.17-7.21)。

這是一個能給程式設計師加buff的公眾號 (CoderBuff)
《ElasticSearch6.x實戰教程》之分詞

相關文章