首先本文只是说说如何将你原有的分词程序与ferret集成,而不是说中文分词的文章,本文也不提供中文分词程序下载如果您为了这两个目的可以不用继续读下去了。。
做
搜索特别是中文搜索肯定会遇到中文分词问题,自己写一个搜索程序没这精力估计也没这能力,所以还是用还是用些Lucene
,nutch之类的再hack一下估计能完成初期的大部分要求(以后?如果有以后的话再自己发展或开发吧。刚开始的时候要学会行进中开火,用最小的成本换
取最大的收益)
因为不喜欢java所以Lucene或nutch不在考虑范围之类,最近玩ruby所以找到了ferret一个Lucene的ruby实现。主要代码全部使用C编写根据网上文档及个人测试性能还是很不错的:
Ferret
is a high-performance, full-featured text search engine library written
for Ruby. It is inspired by Apache Lucene Java project.
官方wiki:http://ferret.davebalmain.com/trac/
像这些程序一般不支持中文分词所以直接拿来是不能用的,最简单办法是写一个自己的分词算法或是把已有的集成进去。怎么实现呢?
经过两天的坚苦尝试后终于可以用了,虽然感觉不是那么好,但是至少可以用了不是吗?两天来的艰苦努力只得出一个结论:
很多时候我们放弃最简单的途径而去追求所谓的完美,到头来又回到原点。
最初的想法是在添加文档(add_document)或进行搜索的时候先把传进来的数据进行分词,再传给ferret,但是想来想去觉得这种方法不是很完美,还有如果高级搜索的时候还要去分析关键字,觉得麻烦,被我否了(后来还是用了这种方法)
以下内容没什么用,只是记录我的演讲过程和源代码阅读结果。
因为它原来都是用C写的,那就自己写一个Analyzer吧,OK,在短暂的看了看它的代码后,发现有些复杂(其实并不是那么复杂,只是最初不熟悉怎么写ruby扩展,加上者的代码风格和我的相去甚远有些东西没看明白),于是想偷懒一下修改或是扩展.
最简单尝试:
def MyAnalyzer < Analyzer
def token_stream(field, str)
#在这里将str分词,分成空格间隔的字符串再给WhiteSpaceTokenizer分解
return WhiteSpaceTokenizer.new(str,@lower)
end
end
想
法不错,但是add_document的时候token_stream(field,
str)传进来的str始终是空的,怎么回事?一个晚上学ruby
ext的写法,然后分析原来的WhiteSpaceAnalyzer再到GDB源程序,终于明白了,原来它在构建token_stream是这样子的
token_stream(field_name,
""),后面的代码再设置doc的内容到text属性里去,再通过text进行处理(C代码就不贴了,忘记在哪懒得找了).否了。
回
到hack
C程序上,改AsciiWhiteSpaceAnalyzer上去,发现在ts_reset()函数里可以取到输入的文档内容,于是在这里分词吧。看上去
可行。可是结果就是搜索不出结果,继续读代码原来这样改动了document的内容,结果是能正确分词,但是在Index中是记录的token的位置,所
以搜索不出结果。否了。
现在呢?两天过去了,没有什么实质进展只是对于程序大概了解了。现在叫我自己写一个Analizer已经不是
大问题了,可是我原来的分词算法有些复杂,加载字典也需要一些时间我不想每次都加载,想做个socket server再用socket
client进行分词操作,如果把这些整进ferret有些复杂了。于是自己写一个也否了。
最后回到原点。在把文档和关键词传进ferret的时候先分好词吧。。。呵呵。。。整个事件就是如此。
OK,说到重点,我用rails,所以用了acts_as_ferret plugins,hack一下这个倒是简单半个小时不到完成任务,唉,前面两天何苦呢。
改acts_as_ferret/lib下的class_methods.rb
define_to_field_method函数:
val = content_for_field_name(field)
改成后面把val分一下词就OK了
http=Net::HTTP.start( '127.0.0.1', 7790)
response = http.get( '/?content='+URI.escape(val) )
val = response.body
acts_as_ferret的hack就完成了。
再就是查询的时候在find_by_contents之前把关键字也分词一下。。。
很多时候我们放弃最简单的途径而去追求所谓的完美,到头来又回到原点。
ruby on rails 中 acts_as_ferret 搜索结果的分页:
1.Model中定义:
def self.full_text_search(q, options = {})
return nil if q.nil? or q==”"
default_options = {:limit => 10, :page => 1}
options = default_options.merge options
options[:offset] = options[:limit] * (options.delete(:page).to_i-1)
results = Article.find_by_contents(q, options)
return [results.total_hits, results]
end
2.application.rb
def pages_for(size, options = {})
default_options = {:per_page => 10}
options = default_options.merge options
pages = Paginator.new self, size, options[:per_page], (params[:page]||1)
pages
end
3 搜索的时候:
def search
@query=params[:query]
@total, @articles = Article.full_text_search(@query, :page => (params[:page]||1))
@pages = pages_for(@total)
end
4.view中:
< %= pagination_links(@pages, :params => {:query=>@query}) %>
详细参考:
http://blog.zmok.net/articles/2006/10/18/full-text-search-in-ruby-on-rails-3-ferret |