The Old Blog Archive (Traditional Chinese), 2004-2009

acts_as_ferret: Rails全文搜尋快速上手(與中日韓文支援)

Here are some code snippets that I use to add CJK (Chinese, Japanese and Korean) characters support to Ferret and acts_as_ferret.

(Update: lingr.com has released their multilingual analyzer, see http://blog.lingr.com/2007/05/a_new_plugin.html for detail).

相信許多鐵道迷都聽過雪貂(Ferret)。雪貂是一套根據Lucene所開發的全文搜尋引擎。裝上了「化身為雪貂」(acts_as_ferret)這套plug-in之後就更厲害了,任何ActiveRecord model只要加上輕量之人最愛的神秘一行,瞬間就具有了全文搜尋能力。

Ferret是用C寫成的,用語和基本觀念與Lucene一致。因此對Lucene有認識的朋友應該很容易上手。雖然說化身為雪貂很好用,不過O’Reilly的Ferret一書仍有一讀的必要。該書最後還介紹如何配合其他plug-in來index諸如PDF, JPEG EXIF等metadata,幾乎可以寫一套小型的Mac OS X Spotlight。而該書對於Ferret的構成、內部運作原理、performance tuning的介紹,也相當實用(而且,不需要先學Lucene;我也還在研讀這一部份就是了……)。

以下是我用的 RegExpAnalyzer,僅僅很簡單的把歐語的單詞拆開、數字拆開,中日韓文則以字元方式來索引。這種簡單的中日韓文tokenizing在搜尋精確度不要求高的場合,大體能用。要更好的搜尋結果,或是要做到同音字搜尋、簡繁搜尋,當然就需要更複雜的 Analyzer。

請找個地方填入以下的 regex 跟 Analyzer:

GENERIC_ANALYSIS_REGEX = /([a-zA-Z]|[\\xc0-\xdf][\\x80-\\xbf])+|[0-9]+|[\\xe0-\\xef][\\x80-\\xbf][\\x80-\\xbf]/
GENERIC_ANALYZER = Ferret::Analysis::RegExpAnalyzer.new(GENERIC_ANALYSIS_REGEX, true)

然後在想要加入搜尋的 model 裡加入:

acts_as_ferret({:fields => [ FIELDS_YOU_WANT_TO_INDEX ] }, { :analyzer => GENERIC_ANALYZER })

之所以不把 GENERIC_ANALYZER 放在 acts_as_ferret 裡,除了可重用性的原因,另外還避免掉在 Mongrel + Rails development mode 時可能造成的 bus error / segmentation fault (原因不明)。

總之,只要做完這件事,就可以:

Model.find_by_contents("hola")

acts_as_ferret很聰明,如果是第一次使用,會幫你把這個data model所用table全部讀一遍,建立必要的全文索引。之後所有的 CRUD 動作只要透過這個 model ,「化身為雪貂」會幫你做完所有該做的 Ferret indexing 動作。

先前有一些中文論壇提到用 /./ 來處理中文斷字(說「斷詞」或「分詞」有誤導之嫌)。雖然 Ruby 的 regex engine 在 $KCODE 設為 utf-8 時,可以正確地以 /./ 來掃描 Unicode 字元,但是這樣的作法是有問題的。英文詞因此會被斷成一個字元一個字元。而,單純用 [a-zA-Z] 則忽略了歐語,這是不夠的。

偏偏 Ruby 的 Unicode 支援只做了一半,不像 Perl 可以用 /\x{80}-\x{7ff}/ 的方式來表達 Unicode range,所以我們得祭出 jcode.rb 裡處理 UTF-8 的 regex (也就是利用 UTF-8 的特性),來找出實際上為 U+80 ~ U+7FF 以及 U+800 ~ U+FFFF 的字元。當然,> U+FFFF 的字元這裡並沒有處理,而且這個方式其實過於簡化。

但總之這是可以用的方法。如果要測試或改善 regex ,可以使用Ferret一書中第65頁所列的以下方法來測試:

def test_token_stream(token_stream)
  puts "Start | End | PosInc | Text"
  while t = token_stream.next
    puts "%5d |%4d |%5d   | %s" % [t.start, t.end, t.pos_inc, t.text]
  end
end

然後在irb中:

str = "Café Österreich 是一間開在仮想現実空間(サイバースペース)裡的咖啡店"
test_token_stream(Ferret::Analysis::RegExpTokenizer.new(str, GENERIC_ANALYSIS_REGEX))

就可以看到 RegExpTokenizer 執行的效果。

5 Responses to “acts_as_ferret: Rails全文搜尋快速上手(與中日韓文支援)”

  1. on 18 May 2007 at 10:58Hi! I'm clsung

    快快樂樂學 Ruby – 再談 Ferret…

    這只是為了要呼應之前的舊文章…

    話說昨天早上到了實驗室,發現 b6s 前一晚留給我有關 Ferret 的訊息。兩點,第一是 lukhnos實作了 Ferret 處理中文的方法,在這裡。當然,處理中文很重要….

  2. [...] http://lukhnos.org/blog/zh/archives/501 [...]

  3. on 22 Jun 2007 at 17:17zhongwei

    你好! 我用webrick rails 开发模式 也出现 segmentation fault错误!你那两句 ANALYZER应该放在那儿才没问题? 我在国内的网站上看到
    acts_as_ferret({:fields => [:title, :content] },{:analyzer=>Ferret::Analysis::RegExpAnalyzer.new(/./,false)})
    这样到是可以支持中文!但是英文不行了!最终在日本网站上找到一个插件用起来没问题了
    http://svn.lingr.com/plugins/multilingual_ferret_tools/
    但我觉得如果你的办法可行的话这个插件就没什么必要了!
    希望能得到你的答复! 谢谢!

  4. [...] 不過呢,在 rails 上的搜尋問題曾經很困擾我。之前就是在作邪惡研究時手上有大批「中文」資料,可是因為 Rails 上的搜尋引擎 acts_as_ferret 老是無法套中文,後來看到燈哥寫了中文斷詞法,心中躍躍欲試,不過最後還是因為靈異原因沒有辦法斷……囧。所以我每次遇到這個問題能閃就閃。 [...]

  5. on 21 Mar 2008 at 21:56id

    http://rmmseg.rubyforge.org/

    RMMSeg is an implementation of MMSEG Chinese word segmentation algorithm.

    RMMSeg can be used as either a stand alone program or an Analyzer of Ferret.