最近手上的專案有一個需求,使用者想對上傳的檔案(簡報、會議記錄、...等)做關鍵字搜尋,腦中閃過的第一個解決方案是 Elasticsearch(ES),但是這得額外架設服務、撰寫程式將檔案內容送進 ES 做索引,要花錢、花時間,使用者不一定買單,所以我就想到 Windows 檔案總管的搜尋功能能不能拿來用?它背後使用的服務是 Windows Search Service(WSS),下關鍵字去 Google 馬上就找到黑大的文章,感謝黑大。
支援的檔案類型
WSS 支援哪些檔案類型?我們可以從 Windows Search 設定
中來看查,以 Windows 10 為例,在搜尋框輸入「Windows Search 設定」,點擊打開視窗之後,往下捲找到「進階搜尋索引子設定」點擊它,在打開的視窗中點擊「進階」,在進階選項中的「檔案類型」頁籤我們就可以找到目前有支援的檔案類型。
我們可以看到支援的檔案類型不少,注意到中間有一個選項「應該如何為此種檔案類型建立索引?
」,如果我們要對檔案內容做索引,就要選擇「索引檔案屬性和內容
」,不過 WSS 已經有針對常見的文字類型副檔名預設選擇該選項,比如說 txt、doc/docx、ptt/pttx、xls/xlsx、pdf、...等,我們想知道哪一個副檔名預設的索引選項是什麼?只要點選該副檔名就可以知道了。
不支援的副檔名我們也可以自己新增,在下面「將新的副檔名新增到清單
」的區域中輸入副檔名,點擊「新增」就可以了。
建立索引
建立索引的方式也很簡單,我們先建立一個資料夾,假設叫 Search
好了,建好之後我們回到「索引選項」的視窗,點擊「修改
」,將剛剛我們建立的資料夾打勾,接著按「確定
」。
再來我們就可以將要建立內容索引的檔案丟進這個資料夾,讓 WSS 建立檔案內容的索引。
※ 在這邊我遇到一個索引內容亂碼的問題,在改用帶有 BOM 的 UTF-8 編碼儲存純文字檔(txt)之後,問題就得到解決了。
關鍵字搜尋
到這邊我們可以開始做關鍵字搜尋了,我們先在檔案總管試著下關鍵字搜尋看看,沒問題的話應該是會成功的。
接下來我們用 C# 撰寫搜尋關鍵字的程式,要對 WSS 做關鍵字搜尋,我們可以選擇我們比較熟悉的 SQL 語法的方式,要 Query 的對象是 SystemIndex
,而要 SELECT 的欄位可以從 Property Mappings (from WDS 2.x to 3.x) 這篇文章去參考,不過它也沒有列出所有可 SELECT 的欄位,剩下的只能透過範例或是其他大大的經驗去得知。
WHERE 條件的部分,我們可以用 DIRECTORY
或 SCOPE
去指定要搜尋的資料夾,差別在於 SCOPE 會搜尋子資料夾,關鍵字則是可以用 CONTAINS()
或是 FREETEXT()
兩個函式來指定,差別在哪?稍後再做解釋,我們先來看程式碼。
首先建立 OleDbConnection
,連線字串固定是 provider=Search.CollatorDSO.1;EXTENDED?PROPERTIES="Application=Windows"
,然後建立 OleDBCommand
去執行查詢,程式碼如下:
DataTable result = new DataTable();
using (var wssConn = new OleDbConnection("provider=Search.CollatorDSO.1;EXTENDED?PROPERTIES=\"Application=Windows\""))
{
wssConn.Open();
var cmd = wssConn.CreateCommand();
cmd.CommandText = @"
SELECT System.ItemName
FROM SystemIndex
WHERE SCOPE = 'D:\Search'
AND CONTAINS('我們身邊')";
var dataAdapter = new OleDbDataAdapter(cmd);
dataAdapter.Fill(result);
}
CONTAINS() 跟 FREETEXT() 的差別在於 CONTAINS() 是把參數看成一個詞
去搜尋,FREETEXT() 則是把參數看成不同詞
去搜尋,舉例來說,我在我要索引的資料夾裡面丟了兩篇文章,這兩篇文章都有我們
及身邊
這兩個詞。
我如果條件是下 CONTAINS('我們身邊') 只會搜尋到左邊的檔案,改下 FREETEXT('我們身邊') 條件則會兩個檔案都找出來,而且只有 CONTAINS() 支援邏輯運算,所以條件 CONTAINS('"我們" OR "身邊"') 的搜尋結果會等同於 FREETEXT('我們身邊')。
我們接著延伸問題,如果要搜尋單一個字「阿
」這個關鍵字,我們會發現不管是下 CONTAINS('阿') 還是 FREETEXT('阿') 都搜尋不出結果,這個就跟 Windows 的分詞系統有關,中文不像英文,英文判斷符號就可以成功分開每一個字詞,但是中文的分詞不管怎麼分都無法百分之百精準,這就導致了有一些字搜不出來,不過還是有辦法可以解決的,可以改用萬用字元搜尋看看,萬用字元搜尋只有 CONTAINS() 函式支援,語法如下:
SELECT System.ItemName
FROM SystemIndex
WHERE SCOPE = 'D:\Search'
AND CONTAINS('"阿*"')
但是,這個萬用字元搜尋也不是真的萬用,星號(*)只能擺在結尾,而且它一樣是依賴 Windows Search 分詞的結果,能夠被分詞出來的詞彙才能被搜尋,像我想要用同樣的手法搜尋「們
」這個字,就完全搜尋不出結果來。
解決問題的第一步,永遠都是面對問題了解背後的 Context,有時候手邊現成的東西或許就能滿足使用者的需求,以上,用 Windows Search Service 做關鍵字搜尋功能就分享給大家,希望對大家有幫助。