首頁

目前文章總數:157 篇

  

最後更新:2024年 12月 07日

0005. 前端全文搜索引擎工具 Lunr.js

日期:2024年 06月 23日

標籤: JavaScript Html Web Lunr.js

摘要:Frontend


解決問題:引入 Lunr.js 前端輕量化工具(不到 100k),可以查詢網站內文的資料
官網說明:Lunr.js 官網連結
開源網址:Lunr.js Github
Demo範例:連結
基本介紹:本篇分為 4 大部分。
第一部分:Lunr.js 介紹
第二部分:完整使用方法
第三部分:Demo 結果
第四部分:引入中文分詞






第一部分:Lunr.js 介紹

Step 1:全文搜尋引擎

Lunr.js 是一個在瀏覽器中使用的小型全文搜尋函式庫。 它索引 JSON 文件並提供一個簡單的搜尋介面,用於檢索與文字查詢最匹配的文件。


Github 上的原文:

Description

Lunr.js is a small, full-text search library for use in the browser. 
It indexes JSON documents and provides a simple search interface for retrieving documents that best match text queries.


讓前端純靜態頁面也能高效率搜尋資料。

Step 2:適合情境

若有以下三種需求,可以考慮引入使用:

1. 靜態網站生成器 Lunr.js 常被用於靜態網站生成器(如 Jekyll、Hugo)中,為生成的靜態網站添加搜索功能。
2. 單頁應用 在單頁應用中,Lunr.js 可以用來在不依賴伺服器的情況下實現快速搜索
3. 文檔網站 用於文檔網站的搜索功能,使用戶能夠快速找到所需的信息。



Step 3:特性

若有以下三種需求,可以考慮引入使用:

1. 輕量級 Lunr.js 無需伺服器支援,所有操作都在客戶端完成,因此特別適合靜態網站和單頁應用(SPA)。
2. 簡單易用 使用 Lunr.js 不需要繁瑣的配置,可以快速上手。通過簡單的 API 可以建立索引和進行搜索。
3. 全文檢索 使用 BM25 演算法,支持全文檢索,能夠處理和搜索大量文本數據。
4. 支持多語言 支持共 14 國語言,包含中文、英文
5. 可擴充性 官方的 API 文件也提供擴充的搜尋方式。





第二部分:完整使用方法

Step 1:官方快速使用教學

官方Guide中有快速使用方式,可以直接參考 或者使用以下準備的 Demo 範例


Step 2:引用 Lunr

下載 lunr.js的 Libary 檔案,約100 kb
在 Html 的 Head 中引入:

<script src="https://github.com/olivernn/lunr.js/blob/master/lunr.js"></script>



Step 3:lunr 建立索引、基本搜索

從 javascript 中,建立一個索引的資料集 documents 變數:

//建立網站資料,作為索引
var documents = [{
  "name": "Lunr",
  "text": "Like Solr, but much smaller, and not as bright."
}, {
  "name": "React",
  "text": "A JavaScript library for building user interfaces."
}, {
  "name": "Lodash",
  "text": "A modern JavaScript utility library delivering modularity, performance & extras."
}, {
  "name": "HTML",
  "text": "JavaScript 是一個成熟的動態程式語言,應用於HTML 文件(document)"
}, {
  "name": "Javascript",
  "text": "JavaScript 是一個成熟的動態程式語言,應用於HTML 文件(document)"
}, {
  "name": "Test",
  "text": "JavaScript JavaProgram 123f53. ehubz,ewui" 
}]


然後建立以下基本搜索代碼

// 1-1. lunr 基本搜索
var idx = lunr(function () {
  this.ref('name')
  this.field('text')

  documents.forEach(function (doc) {
    this.add(doc)
  }, this)
})



Step 4:建立 Lunr 支持的搜索

以下是 6 種 Lunr.js 支持的搜尋方式

// 1-2. 轉換對應查詢方法
function MatchSearch(term, searchName) {
    switch(searchName) {
	case 'base':
	    return term;		    
	case 'wildcards':		    
	    return '*' + term + '*';//包含
	case 'fields':	
        return 'text:' + term;//只查指定 fields
	case 'boots':		    
	    return term + '^10';//增加10倍權重
	case 'fuzzy':		
        return term + '~1'; //1個char的模糊
	case 'presence':		    
	    return '+' + term + ' +JavaProgram';// 包含查詢的內容 && 包含有 JavaProgram 資料
	}
	return term;
}



Step 5:Html 顯示索引資料

將 documents 索引資料轉成 Table Dom 元件顯示,便於觀察

// 2. 轉成畫面上的 Table 顯示
function createTable() {
    var table = document.createElement("table");
    var headerRow = document.createElement("tr");
     table.style.borderCollapse = "collapse";
	 
    // Create header cells
    for (var key in documents[0]) {
        var headerCell = document.createElement("th");
        headerCell.textContent = key;
	    headerCell.style.border = "1px solid #dddddd";
        headerRow.appendChild(headerCell);
    }
    table.appendChild(headerRow);
    
    // Create data rows
    documents.forEach(function(documentItems) {
        var row = document.createElement("tr");
        for (var key in documentItems) {
            var cell = document.createElement("td");
            cell.textContent = documentItems[key];
			cell.style.border = "1px solid #dddddd";
            row.appendChild(cell);
        }
        table.appendChild(row);
    });
    
    return table;
}



Step 6:查詢結果條列

執行查詢結果後顯示[筆數]、[條列]結果

// 3-1. lunr 的查詢結果顯示成條列
function lunr_search(term, searchName) {
    document.getElementById('lunrsearchresults').innerHTML = '<ul></ul>';
    if(term) {
        // 3-2. 替換為搜尋方式的字詞
		term = MatchSearch(term, searchName);
		var results = idx.search(term);
        document.getElementById('lunrsearchresults').innerHTML = "<p>共" + results.length + "筆,如下:</p>" + document.getElementById('lunrsearchresults').innerHTML;
        //put results on the screen.
        if (results.length > 0) {               
            for (var i = 0; i < results.length; i++) {
                var ref = results[i]['ref'];
                var name = documents.find(doc => doc.name === ref).name;
                var text = documents.find(doc => doc.name === ref).text;
                document.querySelectorAll('#lunrsearchresults ul')[0].innerHTML = document.querySelectorAll('#lunrsearchresults ul')[0].innerHTML + "<li class='lunrsearchresult'><a href=''><span class='title'>" + (i + 1) + ". " + name + "</span><br /><span class='body'>" + text + "</span><br /><span class='url'></span></a></li>";
            }
        } else {
            document.querySelectorAll('#lunrsearchresults ul')[0].innerHTML = "<li class='lunrsearchresult'>查無結果</li>";
        }
    }
    return false;
}



Step 7:Html Body內容

Html 上的所有元件建立如下:

<style>
    #lunrsearchresults {padding-top: 0.2rem;}
    .lunrsearchresult {padding-bottom: 1rem;}
    .lunrsearchresult .title {color: #d9230f;}
    .lunrsearchresult .url {color: silver;}
    .lunrsearchresult a {display: block; color: #777;}
    .lunrsearchresult a:hover, .lunrsearchresult a:focus {text-decoration: none;}
    .lunrsearchresult a:hover .title {text-decoration: underline;}
</style>


<form>
    <div id="buttonWrapper" style="display: flex;">
	    <p><button type="button" onclick="lunr_search(document.getElementById('lunrsearch').value , 'base');">Lunr 基本搜索</button></p>
		<p><button type="button" onclick="lunr_search(document.getElementById('lunrsearch').value , 'wildcards');">通配符(wildcards) 搜索</button></p>
		<p><button type="button" onclick="lunr_search(document.getElementById('lunrsearch').value , 'fields');">字段(fields) 搜索</button></p>
		<p><button type="button" onclick="lunr_search(document.getElementById('lunrsearch').value , 'boots');">權重提升(boosts) 搜索</button></p>
		<p><button type="button" onclick="lunr_search(document.getElementById('lunrsearch').value , 'fuzzy');">模糊匹配(fuzzy matches) 搜索</button></p>
		<p><button type="button" onclick="lunr_search(document.getElementById('lunrsearch').value , 'presence');">指定關鍵詞(term presence) 搜索</button></p>
    </div>
	<p><input type="text" class="form-control" id="lunrsearch" name="q" maxlength="255" value="" placeholder="查詢關鍵字" /></p>
</form>

<div id="lunrsearchresults">
    <ul></ul>
</div>

<br/><br/><hr><br/><br/>

<div id="contentboard">
    



Step 8:範例畫面

Demo連結上方有 6 個搜尋方法
中間是輸入查詢資料
最下方是當前的所有已建立的索引資料




第三部分:Demo 結果

Step 1:Lunr 基本搜索

輸入 Javascript 後,可以找到 5 筆完全匹配此文字的資料


Step 2:通配符(wildcards) 搜索

輸入 small 後,因為匹配了 * 所以會變成 small* 查詢,因此可以找到 1 筆 Text內含有的資料

case 'wildcards':		    
    return '*' + term + '*';//包含




Step 3:字段(fields) 搜索

針對 text: 查詢,如果有出現在 text 中才會查詢到資料

case 'fields':	
    return 'text:' + term;//只查指定 fields




Step 4:權重提升(boosts) 搜索

boosts說明 提高某個搜尋的字詞比其他字詞更高的價值,使期在特定條件下容易搜尋到
以此例來說,輸入的字詞增加 10 倍權重,但需對後面資料賦予單詞。

case 'boots':		    
    return term + '^10';//增加10倍權重




Step 5:模糊匹配(fuzzy matches) 搜索

針對 1 個字元的模糊搜尋,以 uch 舉例,省略了 m 字元,使其可以找到含有 much 的資料

case 'fuzzy':		
    return term + '~1'; //1個char的模糊




Step 6:指定關鍵詞(term presence) 搜索

用 + 代表必須包含,用 - 代表不包含,以此例就是查詢 Test 並且包含有 JavaProgram 的資料

case 'presence':		    
    return '+' + term + ' +JavaProgram';// 包含查詢的內容 && 包含有 JavaProgram 資料






第四部分:引入中文分詞

Step 1:Lunr 基本搜索 - 無法查中文

輸入 ‘文件’ 理論上應該要可以搜尋到 2 筆資料,但預設的 Lunr.js 是不支援中文


Step 2:引入中文 Lunr-chinese

Lunr-Chinese 的 GitHub有分享使用中文分隔字詞的搜尋
替換原本引用的 lunr.js 為以下

<script src="https://raw.githubusercontent.com/Wiredcraft/lunr-chinese/master/lunr-chinese.js"></script>



Step 3:Lunr 基本搜索 - 可查詢中文

可以發現中文字詞能正確查找了