iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0
Modern Web

手刻部落格,從設計到部署的實戰攻略系列 第 27

站內搜尋(三):搜尋流程,MiniSearch + Jieba

  • 分享至 

  • xImage
  •  

前兩講我們討論了搜尋的基本原理斷詞,帶著這些基本知識,這一講便來聊聊在我們的 Astro 專案中搜尋功能的主要流程。

挑選純前端搜尋套件

現存的套件有蠻多種的,像是 Astro 官方主題用的 Pagefind、老牌的 Lunr.js、主打高性能的 Flex Search 和彈性較高的 MiniSearch 等等。

由於個人部落格的文章不會太多,各家套件的效能在這個量級比較起來其實差不多,那麼挑選的著重點就在於功能了。

剛開始有考慮過使用 Pagefind,因為套用在我們的專案中還算簡單,能夠分片(Sharding)且被 Astro 官方使用,但是在中文斷詞這塊就不太好客製。

因此最終選擇了 MiniSearch + Jieba 斷詞,基本上就能將所有流程都涵蓋在內,自己讀取文章、斷詞、Highlight 等等。

search-data.json 文件

先來看看要在前端搜尋,我們需要提供怎麼樣的資訊?

這是一份叫做 search-data.json 的 JSON 檔案,包含 indexdocs 兩個欄位,index 放的是序列化的 MiniSearch 索引字串,而 docs 則包含文章 idtitleurl 等等。

實際的 JSON 看起來像是:

{
    "index": "{\"documentCount\":33,\"nextId\":33,\"documentIds\":{\"0\":\"astro-basic\",\"1\":\"astro-components-layouts\",...",
    "docs": [
        {
            "id": "astro-basic",
            "title": "Astro(一):輕又快的靜態網站產生器,淺談島嶼架構",
            "url": "",
            "tags": ["frontend", "javascript", "css"]
        },
        {
            "id": "astro-components-layouts",
            "title": "Astro(二):初始化專案,理解 Astro Components 及 Layouts 基礎",
            "url": "",
            "tags": ["frontend", "javascript"]
        }
    ]

其中的 docs 比較好理解,就是所有文章的基本資訊;而 index 則包含 documentIdsfieldIdsstoredFields 等,能讓 MiniSearch 在前端迅速復原索引和權重等重要資訊。

搜尋流程

有了 search-data.json 後,我們可以在前端頁面載入時就順便下載這個檔案,透過 MiniSearch.loadJSON(index) 讀回所需資訊。

接著實作一個搜尋視窗,在 Input Element 中輸入任何文字時呼叫 mini.search(value) 取得每一筆資料的結果,包含 idscorematchterms 等等。

例如搜尋「執行」這個關鍵字,我們可以找到包含這個關鍵字的文章,不論他在標題或內文。

搜尋「執行」示意圖

*搜尋「執行」示意圖

所謂的 terms 是實際被索引到的字詞(經過我們斷詞後的),在這個例子中就是「執行」二字; score 則是這筆文章的相關度分數,我們可以依據這個分數來排序搜尋結果。

match 則是一個物件,key 是被命中的詞,value 為這個詞出現在哪些欄位。以圖中第一筆文章為例,是 { '執行': ['tokenizedTitle', 'tokenizedContent'] },代表同時命中標題和內文。

完整的搜尋結果是:

{
  "id": "docker-cmd-entrypoint-multiple-processes",
  "score": 7.20,
  "terms": ["執行"],
  "queryTerms": ["執行"],
  "match": { "執行": ["tokenizedTitle", "tokenizedContent"] },
  "title": "Docker CMD 及 ENTRYPOINT,以及如何在 Docker 同時執行多個程序",
  "tags": ["docker"]
}

值得一提的是,如果我們換一種方式來搜尋,像是剛剛這標題中有「同時執行多個程序」的字樣,我們僅僅擷取「行多」這個沒有什麼意義的組合字試試。

搜尋失敗示意圖

*搜尋失敗示意圖

就會發現找不到相關文章,這是由於我們並非全文字串完全的一一比對,而是根據常用中文的字詞作為索引,藉此提升搜尋效率。

前置流程

而在實際能搜尋之前,這份 search-data.json 的文件就需要在建置的流程中產出,如此一來才能提供 MiniSearch 所需的資訊給前端使用。

我們首先可以建立一個 build-search-docs.ts 檔案,先把所有 Markdown 文章抓出來,以此專案為例,從 src/content/blog 裡面擷取內容,並抽出標題、網址、標籤等等。

再來就需要透過 nodejieba 來做中文斷詞了,將標題、內文、標籤拆成一串字詞,然後用空白串起來。例如「Docker 同時執行」拆成含有空白的「Docker 同時 執行」,這就是 MiniSearch 建立索引的基礎。

最後將每篇文章變成一個搜尋物件,包含 titleurltags 等資訊,打包成一個 search-data.json 檔,就如同上一章節的範例所示。

這樣一來,就能在部落格中對中文做算是不錯的站內搜尋功能了!

參考資料

  1. GitHub - MiniSearch
  2. Pagefind
  3. GitHub - Flex Search
  4. Lunr.js
  5. GitHub - nodejieba

上一篇
站內搜尋(二):何謂斷詞、記號化 Tokenization?
下一篇
部署(一):部落格架在何處?建置和部署服務的選擇
系列文
手刻部落格,從設計到部署的實戰攻略29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言