Elasticsearch 支援繁體中文分詞的方法 === 知道的人都知道,elasticsearch 作為目前搜尋引擎中效能最好的資料庫,如果要使用搜尋功能時,第一個想到的工具都是他,不過預設的引擎分詞只支持英文,也就造成中文搜尋的效果不是很好,我們來舉個例子說明什麼是分詞,以及為什麼效果不好? 中文分詞的效果 --- 這裡假設你已經對 Elasticsearch 的 RESTful API 有一定的了解(其實我也不是很了解,反正現查現用就好😆),來看一下如果用預設的分詞工具會分出什麼結果,這是我使用的 API: ![截圖 2025-02-01 19.58.48](https://hackmd.io/_uploads/HycYhtjukx.png) 回應的結果如下: ```json { "tokens": [ { "token": "早", "start_offset": 0, "end_offset": 1, "type": "<IDEOGRAPHIC>", "position": 0 }, { "token": "上", "start_offset": 1, "end_offset": 2, "type": "<IDEOGRAPHIC>", "position": 1 }, { "token": "好", "start_offset": 2, "end_offset": 3, "type": "<IDEOGRAPHIC>", "position": 2 }, { "token": "中", "start_offset": 3, "end_offset": 4, "type": "<IDEOGRAPHIC>", "position": 3 }, { "token": "國", "start_offset": 4, "end_offset": 5, "type": "<IDEOGRAPHIC>", "position": 4 }, { "token": "現", "start_offset": 6, "end_offset": 7, "type": "<IDEOGRAPHIC>", "position": 5 }, { "token": "在", "start_offset": 7, "end_offset": 8, "type": "<IDEOGRAPHIC>", "position": 6 }, { "token": "我", "start_offset": 8, "end_offset": 9, "type": "<IDEOGRAPHIC>", "position": 7 }, { "token": "有", "start_offset": 9, "end_offset": 10, "type": "<IDEOGRAPHIC>", "position": 8 }, { "token": "一", "start_offset": 10, "end_offset": 11, "type": "<IDEOGRAPHIC>", "position": 9 }, { "token": "個", "start_offset": 11, "end_offset": 12, "type": "<IDEOGRAPHIC>", "position": 10 }, { "token": "冰", "start_offset": 12, "end_offset": 13, "type": "<IDEOGRAPHIC>", "position": 11 }, { "token": "淇", "start_offset": 13, "end_offset": 14, "type": "<IDEOGRAPHIC>", "position": 12 }, { "token": "淋", "start_offset": 14, "end_offset": 15, "type": "<IDEOGRAPHIC>", "position": 13 } ] } ``` 從結果中可以發現,每一個字都被拆分成一個詞了,這樣為什麼會說效果不好?舉個例子,如果今天用戶搜尋「電腦」,出來的結果可能會有電腦、電視、電風扇,因為他們都有「電」字,顯然出現了很多無關的資料,也就造成搜尋效果不佳。那麼經過處理之後呢?我們再來看一下處理過後的結果,先看一下我使用的 API: ![截圖 2025-02-01 20.04.32](https://hackmd.io/_uploads/SJUxAKid1x.png) 這是出來的結果: ```json { "tokens": [ { "token": "早上好", "start_offset": 0, "end_offset": 3, "type": "CN_WORD", "position": 0 }, { "token": "中國", "start_offset": 3, "end_offset": 5, "type": "CN_WORD", "position": 1 }, { "token": "現在", "start_offset": 6, "end_offset": 8, "type": "CN_WORD", "position": 2 }, { "token": "我", "start_offset": 8, "end_offset": 9, "type": "CN_CHAR", "position": 3 }, { "token": "有", "start_offset": 9, "end_offset": 10, "type": "CN_CHAR", "position": 4 }, { "token": "一個", "start_offset": 10, "end_offset": 12, "type": "CN_WORD", "position": 5 }, { "token": "冰淇淋", "start_offset": 12, "end_offset": 15, "type": "CN_WORD", "position": 6 } ] } ``` 這就是所謂的分詞,利用中文習慣的詞語來拆分資料,這樣相對來說在做資料比對時就可以更精準了,以上面的例子來說,「電腦」會被拆分為「電腦」,那麼資料庫中有```電腦```這個詞的資料就會被調出來,而不會出現電視或者電風扇。 如何支援中文以及其原理 --- 透過上面的例子不知道你有沒有發現,上面兩個 API 其實只是換了 ```analyzer``` 就實現中文分詞了,但如果你只是下載 Elasticsearch 後運行,你應該會發現沒辦法像我一樣使用第二種,這是因為我們必須要安裝中文分詞插件。 目前有維護且開源的中文分詞插件就是 [IK 分詞工具](https://github.com/infinilabs/analysis-ik),不過很大的問題就是這個工具他只支援簡體中文,所以對於繁體的用戶來說,我們還需要一些處理才能使用這個工具。 至於為什麼只支援簡體?主要原因是實現這個分詞的方法靠的是「詞庫」,也就是這個插件維護了一個巨量的中文詞彙檔案,透過比對輸入的詞及詞庫中的詞來拆分語句,很不幸的這個詞庫裡面的詞都是簡體中文,而且習慣用語也是大陸用語,所以對繁體中文地區的用戶仍然沒辦法很好的分詞。 有了以上的理解後其實就有解決思路了:把詞庫轉成繁體中文,那就可以支援繁體中文啦! 這第一個方法是我以前的做法,也就是說其實這個做法還是存在某些問題的,例如你不能保證你的用戶和你的資料一定都是繁體中文,也有可能有簡體中文的用戶搜尋,那麼它就搜尋不到想要的資料。 不過下面我還是簡單說明一下這個是怎麼做到的,以及一步步的安裝教學!(這也是我寫這篇文章的初衷,目前還沒有看到支援繁體中文的完整安裝教學,摸了好一陣才摸出一個所以然來) 來安裝 Elasticsearch 及 IK 分詞插件並轉為繁體中文吧 --- 首先我還是推薦使用 docker 安裝 Elasticsearch,因為環境隔離且單純,要刪除時也是直接刪除 image 就好,所以接下來我都會以 docker 來做示範。 我們要運行 Elasticsearch 之前,需要先拉取鏡像(image),拉取的時候要注意版本,因為接下來的 IK 分詞工具的版本也要和 Elasticsearch 的版本相同。 ```shell docker pull elasticsearch:8.17.1 ``` 透過這行指令可以安裝 elasticsearch 的官方鏡像,以現在時間 2025/2/1 來說最新的版本是 8.17.1,那麼我們就下載這個,不過下載之前要注意 [IK 分詞工具版本](https://release.infinilabs.com/analysis-ik/stable/)是否也同步更新到最新版了,他更新有可能比較慢,所以還是以 IK 的最新版為準。 在開發環境運行 ES 時需要添加兩個環境變數: ```yaml environment: discovery.type: single-node xpack.security.enabled: false ``` 另外如果你是 Mac 電腦 M4 系列芯片搭配 macOS 15.2 版本有一個 bug,需要額外設定兩個環境變數才能正常執行(問就是因為我是受害者🙂‍↕️): ```yaml environment: ES_JAVA_OPTS: -XX:UseSVE=0 CLI_JAVA_OPTS: -XX:UseSVE=0 ``` 至於運行的方法,你可以使用 docker desktop,或者 docker compose 或者單純的指令也行隨你高興,基本上你看到一堆 log 出來就算是正確執行了。 運行成功之後我們要進到容器裡面安裝插件,使用以下指令: ```bash docker exec -it <container_id> /bin/bash ``` 然後運行命令: ```bash /usr/share/elasticsearch/bin/elasticsearch-plugin install https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.17.1.zip ``` 注意這裡最後版本號,需要對應你的 ES 版本,安裝好後重啟容器,可以透過以下指令確認是否安裝成功: ```bash /usr/share/elasticsearch/bin/elasticsearch-plugin list ``` 如果有出現 ik-analyzer 就代表安裝成功了,再來我們需要以 root 角色進入容器中: ```bash docker exec -u root -it <container_id> /bin/bash ``` 然後我們用開源專案 [OpenCC](https://github.com/BYVoid/OpenCC) 將詞庫從簡體轉為繁體,跟著下面的命令一行一行做即可: ```bash apt-get update apt-get install opencc # s2tw 簡體轉繁體 # s2twp 簡體轉繁體+轉換臺灣常用詞彙 cd /usr/share/elasticsearch/config/analysis-ik opencc -c s2twp.json -i main.dic -o /tmp/main-tw.dic cp main.dic /tmp/main-cn.dic cat /tmp/main-tw.dic /tmp/main-cn.dic | sort | uniq > main.dic ``` 這裡還是推薦用 ```s2twp```,他會幫你把慣用語換成台灣習慣的說法,例如:数据库->資料庫,純粹轉換就會是「數據庫」。 重啟一下你的容器,你就獲得一個可以支援繁體中文的 ES 啦!只要在 ```analyzer``` 輸入 ```ik_smart``` 或者 ```ik_max_word``` 就可以體驗中文分詞的效果,建立 index 時也可以直接使用這兩個分析器讓你的內部資料可以正確的中文分詞。 其實還有更簡單的方法 —— stconvert --- 以上是當初自己胡搞想出來的解法,結果有一天隨意亂逛發現,原來 IK 分詞器就有另外一個插件是給繁體中文用戶轉換的了,這樣就不需要對詞庫進行翻譯,而是透過轉換的方法,將用戶輸入的文字以及資料庫內的資料都先轉成簡體用詞,然後再去詞庫比對,比對完再把結果返回給用戶,這個插件就是 ```stconvert```,我們可以用相同的指令安裝一下這個插件: ```bash /usr/share/elasticsearch/bin/elasticsearch-plugin install https://release.infinilabs.com/analysis-stconvert/stable/elasticsearch-analysis-stconvert-8.17.1.zip ``` 用這個插件的好處是,無論用戶或者資料是簡體或者繁體都可以有很好的分詞支援,這樣你的用戶就可以不限於台灣或者大陸,可以給所有的中文使用者使用,不過使用這個插件時定義 index 會比較複雜一點,下面給出例子,你可以根據自己的需求再進行調整: ```json PUT /traditional_index { "settings": { "analysis": { "analyzer": { "ik_traditional_analyzer": { "type": "custom", "tokenizer": "ik_smart", "filter": ["stconvert_filter"] } }, "filter": { "stconvert_filter": { "type": "stconvert", "convert_type": "t2s", "keep_both": false } } } } } ``` ```t2s``` 代表繁體轉簡體,也就是用戶輸入繁體會轉為簡體,當然如果輸入的本來就是簡體,那麼就不會被轉換。然後搜尋的時候我們可以這麼搜尋: ```json GET /traditional_index/_search { "query": { "match": { "content": { "query": "早上好中國", "analyzer": "ik_traditional_analyzer" } } } } ``` 這樣就可以用上我們自己選擇好的插件,讓簡體及繁體都有很好的分詞支援。 以上就是所有讓 Elasticsearch 支援繁體中文的方法,都是碰了許多問題才找到的解決方法,也許未來這裡的程式會變的不可用,但希望能幫到現在的你!