Elasticsearch 文字分析與多語系魔法 — Analyzer, IK, 中文搜尋
你打開搜尋框,輸入「小籠包」。系統回傳所有包含「小」、「籠」、「包」的結果——小學、鳥籠、背包全跑出來。
這不是 bug。這是你的搜尋引擎不會「讀中文」。
英文有空格,dog 就是 dog,切詞天生容易。中文沒有這種天然分隔符。「台北市政府捷運站」到底是「台北市 / 政府 / 捷運站」,還是「台 / 北 / 市 / 政 / 府 / 捷 / 運 / 站」?Elasticsearch 預設的 standard analyzer 選了後者——逐字切,簡單粗暴,但搜尋品質慘不忍睹。
要讓搜尋引擎真正讀懂中文,得先搞懂 Analyzer 這條流水線。
洗衣店的三道工序
Analyzer 的運作像一家洗衣店。髒衣服進來,經過三道工序,變成乾淨的衣服掛上架。文字進來,經過三道處理,變成可搜尋的 token 存入倒排索引。
第一道:Char Filter(預處理)。衣服進門先掃一遍——有沒有口袋裡忘了掏的衛生紙、有沒有掛在上面的名牌。對文字來說,就是去掉 HTML 標籤、替換特殊字元、用正則做初步清理。可以有零到多個,依序執行。
第二道:Tokenizer(斷詞)。這是核心——把一整段文字切成一個個獨立的 token。每個 Analyzer 只能有一個 Tokenizer,它決定了切割的邏輯。Standard tokenizer 依 Unicode 規則切;whitespace 只認空格;keyword 完全不切(整串當一個 token)。中文搜尋的戰場就在這裡。
第三道:Token Filter(後處理)。切完之後的精修——轉小寫、移除停用詞(the、is、a 這些高頻但沒意義的詞)、詞幹還原(running → run)、同義詞擴展。可以有零到多個,依序執行。
1 | 原始文字: "<p>I LOVE Elasticsearch!!!</p>" |
三道工序的組合就是一個 Analyzer。換不同的 Tokenizer、加不同的 Filter,搜尋的行為就完全不同。
內建的幾套「洗衣套餐」
Elasticsearch 預裝了幾個 Analyzer,不用設定就能用:
standard(預設值):用 Unicode 規則斷詞,加 lowercase filter。英文表現不錯,中文就是逐字切——「小籠包」變成 [“小”, “籠”, “包”]。90% 的情境先用這個看看效果。
simple:只留字母,數字會被直接吃掉。適合純文字內容,但處理含數字的技術文件會漏東西。
whitespace:只認空格,其他完全不管。”Hello, World!” 會切成 [“Hello,”, “World!”],連標點都保留。
keyword:完全不切。整個字串就是一個 token。適合 email、URL 這種不該被拆開的東西。
怎麼知道文字被切成什麼樣子?用 _analyze API 直接測:
1 | POST _analyze |
八個沒有語義的單字。搜「台北」能命中,但搜「站」也會命中所有含「站」的文件。這就是為什麼中文需要專門的分詞器。
自己組裝一套 Analyzer
內建的不夠用?可以在建立 Index 的時候,從 Char Filter、Tokenizer、Token Filter 三個層級自己挑零件組裝。
1 | PUT /my_custom_index |
有個進階技巧值得一提:寫入和搜尋可以用不同的 Analyzer。寫入的時候用 ik_max_word 盡量多切(召回率高),搜尋的時候用 ik_smart 精準切(精確度高)。設定方式是在 Mapping 裡同時指定 analyzer(寫入用)和 search_analyzer(搜尋用)。
這就像圖書館的索引卡——建索引的時候多列幾種關鍵字,查的時候只用最精確的那個。
IK:中文搜尋的事實標準
IK Analyzer 是 Elasticsearch 中文分詞的老牌方案,生態最成熟、社群最活躍。它提供兩種模式:
ik_smart(智慧切):盡量切出最少、最大的詞組,不重疊。「台北市政府捷運站」→ [“台北市”, “政府”, “捷運站”]。適合搜尋端——你搜「台北市」就是要找台北市,不想被「台北」「北市」干擾。
ik_max_word(最細切):窮舉所有可能的詞組,會重疊。同一句話 → [“台北市”, “台北”, “北市”, “政府”, “捷運站”, “捷運”, “站”]。適合索引端——多切幾種,使用者不管輸入「台北」還是「台北市」都能命中。
安裝要注意一件事:IK 的版本必須跟 Elasticsearch 完全一致。ES 8.17.0 配 IK 8.16.0?啟動直接失敗,錯誤訊息還不明顯,弄了半天才發現是版本不對。踩過這坑的人都記得。
1 | # Docker 環境安裝 |
實戰設定:寫入 ik_max_word、搜尋 ik_smart
1 | PUT /menu_zh |
寫入測試資料、搜尋「小籠包」——IK 會精準命中包含這個詞組的文件,而不是把所有含「小」或「包」的東西都撈出來。
自定詞典:讓分詞器認識你的詞
IK 的內建詞典涵蓋了大部分常見中文詞彙,但你的業務用語它不一定認得。例如「鼎泰豐」如果不在詞典裡,會被切成 [“鼎”, “泰”, “豐”]——三個獨立的字,搜尋體驗直接崩壞。
解法是建立自定詞典:
1 | # /config/analysis-ik/custom.dic |
修改 IKAnalyzer.cfg.xml 加入 <entry key="ext_dict">custom.dic</entry>,重啟 ES 生效。
每次加新詞都要重啟太痛苦?IK 支援遠端詞典熱更新——指向一個 HTTP URL,IK 每 60 秒檢查 Last-Modified / ETag,有變化就自動重載。不用重啟。
但有一個坑:熱更新只影響新寫入的文件。舊文件的倒排索引不會自動重建。要讓新詞對全量資料生效,得跑一次 _update_by_query 或 _reindex。
其他中文分詞方案
IK 不是唯一選擇,但對大多數場景來說是最穩的起手式。
jieba(結巴):Python 生態的主流分詞器,基於 HMM/Viterbi 演算法。如果你的專案已經大量用 Python 做 NLP,jieba 的整合性比 IK 好。
HanLP:功能最強大——命名實體辨識、句法分析、情感分析都包。適合企業級 NLP 應用,但設定複雜度也最高。
NGram / Edge NGram:暴力切割法,不依賴詞典。「elastic」→ [“e”, “el”, “ela”, “elas”, “elast”, “elasti”, “elastic”]。跟語言無關,專門用來做 autocomplete(自動完成)。
ICU Analyzer:基於 Unicode 標準的分割器,多語系混合內容的首選。如果你的資料裡同時有中文、日文、韓文、英文,ICU 比 IK 更適合當底層。
五個一定會踩的坑
版本不匹配:IK 版本跟 ES 差一個 minor version,啟動直接失敗。錯誤訊息不明顯,可能 debug 半天。永遠用完全一致的版本號。
寫入搜尋用同一個 analyzer:兩邊都用 ik_max_word,搜「台北」命中「台北市」、「台北區」、「台北車站」⋯⋯結果太廣。正確做法:寫入 ik_max_word、搜尋 ik_smart。
忘記繁簡體轉換:使用者輸入簡體但資料是繁體(或反過來)。需要在 Token Filter 加繁簡轉換(stconvert plugin),或在應用層統一。
自定詞典太大:超過 100MB 時 IK 載入時間明顯增加,拖慢 ES 啟動。控制詞典大小,或改用遠端詞典 + 熱更新。
更新詞典後舊資料搜不到:熱更新只影響新寫入。全量生效要 reindex。很多人忘了這一步,以為詞典更新就全自動。
Analyzer 的設計哲學其實就是一條流水線——原料進來(原始文字)、經過標準化的工序(Char Filter → Tokenizer → Token Filter)、產出標準化的成品(token)。
工序的組合決定了搜尋的品質。英文的工序簡單,空格切完、轉小寫、去停用詞就差不多了。中文的工序複雜,因為語言本身沒有天然分隔符,你得教機器怎麼「讀」。
IK 是目前最成熟的教法。但不管用什麼分詞器,最重要的 debug 工具就是 _analyze API——在設定 Mapping 之前,先拿你的實際資料測一輪,看看切出來的 token 是不是你預期的。搜尋品質的問題,八成出在 Analyzer 設定上。
參考來源:Elasticsearch 官方文件 — Text Analysis
參考來源:IK Analysis Plugin for Elasticsearch








