用 elasticsearch 做全文搜索

之前有用 IndexTank 做 HKTV Guide 的搜尋功能,但最近 LinkedIn 收購了 IndexTank ,並宣佈 IndexTank API 將在六個月後關閉。所以,就要找可用的代替品了。

我的要求是:

  • 可以全文索引
  • 可以在 Ruby 裡使用
  • 為了避免這種事情再度發生,這個系統要可以在我的機器裡執行

有幾個開源的搜尋系統能滿足我的需求,但其中 elasticsearch 的 REST 和 JSON API 很吸引我,看上去它的設定也夠簡單,所以它是我的首個測試目標。

安裝

elasticsearch 是個由 Java 編寫,基於 Lucene 的 search server。它的安裝很簡單:只要把 zip 檔下載解壓就行,要修改設定也只需要看一個設定檔。

建立索引和搜尋

執行 elasticsearch 後便可以用它的 REST API 去作索引:

curl -XPUT http://localhost:9200/twitter/tweet/1 -d '{
    "user": "kimchy",
    "message": "Trying out elasticsearch, so far so good?"
}'

接著就可以用 search API :

curl -XGET http://localhost:9200/twitter/tweet/_search -d '{
    "query" : {
        "term" : { "user": "kimchy" }
    }
}'

// Search Result
{"took":4,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":1,"max_score":0.30685282,"hits":[{"_index":"twitter","_type":"tweet","_id":"1","_score":0.30685282, "_source" : {
    "user": "kimchy",
    "message": "Trying out elasticsearch, so far so good?"
}}]}}

搜尋中文

以上的例子雖然成功,但如果內文換成中文,卻會發現不能返回正確的結果:

curl -XPUT 'http://localhost:9200/twitter/tweet/2' -d '{
    "user" : "hlb",
    "message" : "真是靠山吃飯山會倒"
}'
curl -XGET http://localhost:9200/twitter/tweet/_search -d '{
    "query" : {
        "term" : { "message": "吃飯" }
    }
}'

// Search Result
{"took":1,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":0,"max_score":null,"hits":[]}}

這是因為 elasticsearch 預設的 analyzer 不支援中文。

設定 Index Mapping

elasticsearch 的 mapping 定義了如何將文件映射到搜尋器。它指定了文件的搜尋特徵 -- 例如哪一些資料可以索引、怎樣把文件 tokenize 等等。

要讓 elasticsearch 支援中文就要把相應的 mapping 的 analyzer 設定為 cjk analyzer

首先移除舊的 Index:

curl -XDELETE 'http://localhost:9200/twitter'

接著設定 Index Mapping:

curl -XPUT 'http://localhost:9200/twitter/' -d '{
    "mappings": {
        "tweet" : {
            "properties" : {
                "user" : {"type" : "string", "index" : "not_analyzed"},
                "message" : {"type" : "string", "null_value" : "na", "index" : "analyzed", "analyzer" : "cjk"}
            }
        }
    }
}'

輸入資料和測試:

curl -XPUT 'http://localhost:9200/twitter/tweet/1' -d '{
    "user" : "hlb",
    "message" : "真是靠山吃飯山會倒"
}'
curl -XGET 'http://localhost:9200/twitter/tweet/_search' -d '{
    "min_score": 0.1,
    "query" : {
        "text" : { "message" : "吃飯" }
    }
}'

// Search Result
{"took":4,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":1,"max_score":0.13561106,"hits":[{"_index":"twitter","_type":"tweet","_id":"1","_score":0.13561106, "_source" : {
    "user" : "hlb",
    "message" : "真是靠山吃飯山會倒"
}}]}}

就這樣一個基本的搜尋系統就可用了!