用 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" : "真是靠山吃飯山會倒"
}}]}}
就這樣一個基本的搜尋系統就可用了!