隨便抄幾段介紹一下Sphinx。
- Sphinx支援高速建立索引(可達10MB/秒,而Lucene建立索引的速度是1.8MB/秒)
- 高效能搜尋(在2-4 GB的文本上搜尋,平均0.1秒內獲得結果)
- 高擴充性(實測最高可對100GB的文本建立索引,單一索引可包含1億條記錄)
- 支援分布式檢索
- 支援基於短語和基於統計的複合結果排序機制
- 支援任意數量的檔案欄位(數值屬性或全文檢索索引屬性)
- 支援不同的搜尋模式(“完全符合”,“短語匹配”和“任一匹配”)
- 支援作為Mysql的儲存引擎
Thinking Sphinx是其Ruby介面,既然railscasts.com的Ryan都推薦,我們沒有理由不去試試這個熱門外掛程式。
http://www.kuqin.com/searchengine/20081204/29401.html
http://www.zhengjinjun.com/index.php/2009/06/28/sphinx搜尋文法/
安裝支援
git clone git://github.com/freelancing-god/thinking-sphinx.git vendor/plugins/thinking_sphinx
如果是gem安裝
config.gem('freelancing-god-thinking-sphinx',:lib => 'thinking_sphinx',:version => '1.1.12')
務必在你的rails項目的根目錄的Rakefile檔案中添加,外掛程式方式則免去這一步!
require 'thinking_sphinx/tasks'
至於sphinx可到這裡下載, http://download.csdn.net/source/1510659,密碼為nasamidesu。(支援中文分詞,官網的要自己打補丁才行!)
解壓後,記得把Sphinx的bin目錄添加到系統變數中。
定義索引
class Topic true indexes first_post.body,:as => :body,:sortable => true indexes author has :created_at,:updated_at,:forum_id,:digest end#……………………………………………………其他實現………………………………………………………………end
class Post :title ,:sortable => true indexes body,:sortable => true indexes author has :created_at,:updated_at,:forum_id end#……………………………………………………其他實現………………………………………………………………end
與Ferret相比,它不能即時更新索引(亦即不能在我們建立更新刪除一個模型,不能重新索引相關資料)。幸好,Delta Indexes(增量索引)的出現解決了這問題。實現增量索引有三種方式:即時索引,定時索引與延時索引。
即時索引
最常用的就是即時索引,它要求我們在被索引的模型中增加一個屬性,名為delta。
ruby script/generate migration AddDeltaToTopic delta:boolean
class AddDeltaToTopic true, :null => false add_column :posts, :delta,:boolean, :default => true, :null => false end def self.down remove_column :topics, :delta remove_column :posts, :delta endend
define_index do indexes :title,:sortable => true indexes first_post.body,:as => :body,:sortable => true indexes author has :created_at,:updated_at,:forum_id,:digest #聲明使用即時索引 set_property :delta => true end
define_index do indexes topic(:title),:as => :title ,:sortable => true indexes body,:sortable => true indexes author has :created_at,:updated_at,:forum_id #聲明使用即時索引 set_property :delta => true end
定時索引
注:這個非我們項目的流程,作為小知識記一記就是!
set_property :delta => :datetime, :threshold => 1.hour
一小時索引一次,實際上是重建所有索引,所以注意間隔時間不要短於建立索引的時間。
預設使用updated_at,這樣就不用給被索引模型添加delta屬性了。
需要結合的重建索引的命令為rake thinking_sphinx:index:delta或rake ts:in:delta
延時索引
set_property :delta => :delayed
待到搜尋時才重建索引。
sphinx索引速度在所有開源搜尋引擎中是最快,能應對這種需求。
需要結合的重建索引的命令為rake thinking_sphinx:delayed_delta或rake ts:dd
建立索引
注:非流程的小知識
以下任一個命令都可以,下面的基本上是上面的簡寫。
rake thinking_sphinx:index #將組建組態檔案 rake thinking_sphinx:index INDEX_ONLY=true #不組建組態檔案rake ts:indexrake ts:in
重建索引
注:非流程的小知識
以下任一個命令都可以,下面的基本上是上面的簡寫。
rake thinking_sphinx:rebuildrake ts:rebuild
設定Thinking Sphinx設定檔
這個可有可無。雖然推崇COC,但對於某些場合中設定檔還是必不可少的。thinking_sphinx會根據我們提供的設定檔產生sphinx的設定檔,利用後者指導引擎去建立索引。在config下建立一檔案sphinx.yml
一個簡單的模板
development: &my_settings enable_star: 1 min_prefix_len: 0 min_infix_len: 2 min_word_len: 1 max_results: 70000 morphology: none listen: localhost:3312 exceptions: D:/Louvre/log/sphinx_exception.log chinese_dictionary: I:/sphinx/bin/xdicttest: 產生Sphinx設定檔以下隨便選一個。
rake thinking_sphinx:configurerake ts:confrake ts:config
隨便運行上面一個,就可以看到config檔案夾中多出一個檔案development.sphinx.conf
修改
searchd{ address = 127.0.0.1:3312 port = 3312 …………………………………………………………}為
searchd{ listen = 127.0.0.1:3312 …………………………………………………………}搜尋charset_type = utf-8,在其下面添加下面添加chinese_dictionary = I:/sphinx/bin/xdict,chinese_dictionary是指定分詞詞典的選項,包括路徑和檔案名稱。有幾個charset_type = utf-8,就添加幾個chinese_dictionary = I:/sphinx/bin/xdict(通常位於index XXXX_core中)
到這裡我們就可以執行索引了
rake ts:in INDEX_ONLY=true
注意,一定要使用NDEX_ONLY=true
索引成功,沒有報錯,大抵是這個樣子。
它索引的速度非常快。單一索引最大可包含1億條記錄,在1千萬條記錄情況下的查詢速度為0.x秒(毫秒級)。Sphinx建立索引的速度為:建立100萬條記錄的索引只需3~4分鐘,建立1000萬條記錄的索引可以在50分鐘內完成,而只包含最新10萬條記錄的增量索引,重建一次只需幾十秒。
建立Search模組ruby script/generate controller search index
添加路由規則
map.online '/seach', :controller => 'seach', :action => 'index'
修改控制器。
class SearchController 修改相應視圖thinking sphink現在還不支援摘要與高亮,不過我們還可以用一些取巧的方法來取代它們,如用truncate縮短顯示的結果,利用rails內建的highlight方法實現高亮。
<% form_tag '/search', :method => :get ,:style => "margin-left:40%" do %> <input type="radio" name="class" value = "topic" <%= @class == "topic"? 'checked="checked"':'' %>>僅主題貼 <input type="radio" name="class" value = "post" <%= @class == "post"? 'checked="checked"':'' %>>所有貼子<br> <p> <%= text_field_tag :query, @query %> <%= submit_tag "搜尋", :name => nil %> </p><% end %><%- if defined? @results -%> <style type="text/css"> .hilite{ color:#0042BD; background:#F345CC; } </style> <div id="search_result"> <%- @results.each do |result| -%> <h3 style="text-align:center;color:red"><%= highlight h(result.title),@query, '<span class="hilite">\1</span>' %></h3> <div><%= highlight simple_format(result.body), @query, '<span class="hilite">\1</span>'%></div> <%- end -%> </div><%- end -%>現在可以測試一下,但務必先啟動rake ts:start才能進行搜尋!關閉時一定要使用rake ts:stop,sphinx是後台啟動並執行。關閉netBeans時不會像mongrel那樣一同關閉,由於忘記關閉再又再開啟一個sphinx進程,會導致各種奇怪的錯誤。
這時,我們搜尋到的結果應該是這個樣子。
分頁
Thinking sphinx是原生支援will_paginate,預設每頁為20條記錄。我們可以修改一下。
@results = Topic.search params[:search], :page => (params[:page] || 1),:per_page => 5
產生的結果是個特殊的集合(!seq:ThinkingSphinx::Collection ),擁有以下屬性:
- @results.total_entries:總記錄數
- @results.total_pages:總頁數
- @results.current_page:當前頁數
- @results.per_page:每頁的記錄數
AJAX分頁
這要用到局部模組。我們建立模板_topic.html,並把功能擴充一下
<h3 style="text-align:center;color:red"><%= highlight h(topic.title),@query, '<span class="hilite">\1</span>' %></h3><div><%= highlight simple_format(topic.body), @query, '<span class="hilite">\1</span>'%></div><p>作者:<%= highlight h(topic.author),@query, '<span class="hilite">\1</span>' %> 發表時間 <%= topic.created_at.to_s(:db) %></p>
局部模板_post.html.erb同上!
上面這樣寫顯得有點亂,我們可以對它進一步封裝,分割出一個視圖助手(helper)。
def hilight(a,b) #a為要高亮的字串,b為高亮部分,預設高亮後的樣式為hilite highlight a,b, '\1' end
修改剛才的局部模板
<h3 style="text-align:center;color:red"><%= link_to hilight(h(topic.title),@query) ,[topic.forum,topic] %></h3><div><%= hilight truncate(topic.body,:length => 260), @query%></div><p>作者:<%= hilight h(topic.author),@query %>點擊率 <%= topic.hits %> 回複 <%= topic.posts_count-1 %><%= topic.digest == true ? "<span style='color:#f00'>精華貼</span>":"" %>發表時間 <%= topic.created_at.to_s(:db) %></p>
<h3 style="text-align:center;color:red"><%= link_to hilight("Re:"+h(post.title),@query) ,[post.forum,post.topic,post] %></h3><div><%= hilight truncate(post.body,:length => 260), @query%></div><p>作者:<%= hilight h(post.author),@query %> 發表時間 <%= post.created_at.to_s(:db) %></p><% form_tag '/search', :method => :get ,:style => "margin-left:40%" do %> <input type="radio" name="class" value = "topic" <%= @class == "topic"? 'checked="checked"':'' %>>僅主題貼 <input type="radio" name="class" value = "post" <%= @class == "post"? 'checked="checked"':'' %>>所有貼子<br> <p> <%= text_field_tag :query, @query %> <%= submit_tag "搜尋", :name => nil %> </p><% end %><%- if defined? @results -%> <style type="text/css"> .hilite{ color:#0042BD; background:#F345CC; } </style> <p>共<span class="quiet"><%= @results.total_entries %></span>條記錄, <span class="quiet"><%= @results.total_pages %></span>頁,現在是第 <span class="quiet" id="current_page"><%= @results.current_page %></span>頁, 每頁最多有<span class="quiet"><%= @results.per_page %></span>條記錄。 </p> <div id="search_result"> <% if @class == "topic" %> <%= render :partial =>"topic",:collection => @results %> <% elsif @class == "post" %> <%= render :partial =>"post",:collection => @results %> <% end %> </div> <%= will_paginate @results ,:previous_label => '上一頁 ',:next_label => '下一頁' %><%- end -%>修改控制器
class SearchController (params[:page] || 1),:per_page => 5 else @results = Post.search @query,:page => (params[:page] || 1),:per_page => 5 end end if request.xhr? render :update do |page| page.select('div.pagination').each do |element| element.replace "#{will_paginate @results,:previous_label => '上一頁 ',:next_label => '下一頁',:renderer => 'RemoteLinkRenderer' }" end page.replace_html 'search_result',:partial => "#{@class}",:collection => @results page.replace_html 'current_page',params[:page] || 1 end end endend在app/helpers中添加remote_link_renderer.rb
class RemoteLinkRenderer url_for(page), :method => :get}.merge(@remote)) endend
添加以下javascript
/*對所有模組類名為pagination的div元素中的連結添加ajax支援,實現ajax分頁*/var modules_ajax_paginate = function(){ var target = $$("div.pagination a"); if(target != null){ target.each(function(element){ $(element).observe('click',function(event){ _modules_ajax_paginate(this); Event.stop(event); }); }); }}/*上面的私人方法*/var _modules_ajax_paginate = function(element){ var h = element.readAttribute("href"); var s= 'authenticity_token=' + window._token; new Ajax.Request(h, { asynchronous:true, evalScripts:true, method:'get', parameters:s });}Event.observe(window, 'load', function() { modules_ajax_paginate(); })至此,我們的項目就基本完成了,下面所有的東西都當作小知識學習一下就是!
多模型查詢
ThinkingSphinx::Search.search "term", :classes => [Post, User, Photo, Event]
指定匹配模式
SPH_MATCH_ALL模式,匹配所有查詢詞(預設模式)。
Topic.search "Ruby Louvre",:match_mode => :allTopic.search "Louvre"
PH_MATCH_ANY模式,匹配查詢詞中的任意一個。
Topic.search "Ruby Louvre", :match_mode => :any
SPH_MATCH_PHRASE模式,將整個查詢看作一個片語,要求按順序完整匹配。
Topic.search "Ruby Louvre", :match_mode => :phrase
SPH_MATCH_BOOLEAN模式,將查詢看作一個布林運算式。
Topic.search "Ruby | Louvre, :match_mode => :boolean
SPH_MATCH_EXTENDED模式,將查詢看作一個Sphinx內部查詢語言的運算式。
User.search "@name pat | @username pat", :match_mode => :extended
加權
為了提高搜尋品質,我們可以對某些模型或欄位進行加權,提高它們的優先度。預設都是1
User.search "pat allan", :field_weights => { :alias => 4, :aka => 2 }ThinkingSphinx::Search.search "pat", :index_weights => { User => 10 }完整例子,修改我們的編輯器。
unless params[:search].blank? @q = params[:search] @results = Topic.search @q, :page => (params[:page] || 1), :per_page => 5, :order => "@relevance DESC,updated_at DESC", :field_weights => { :title => 20 } end修改視圖
<% @results.each_with_weighting do |topic, weight| %> <h3 style="text-align:center;color:red"><%= hilight h(topic.title),@q %></h3> <div><%= hilight simple_format(topic.body), @q %></div> <p>作者:<%= hilight h(topic.author),@q %> 發表時間 <%= topic.created_at.to_s(:db) %> 權重:<%= weight %></p> <% end %>
Weighted MultiModel Search
ThinkingSphinx::Search.search params[:query], :classes => [Article, Term], :limit => 1_000_000, :page => params[:page] || 1, :per_page => 20, :index_weights => { "article_core" => 100, "article_delta" => 100, } 相關文法
@results.each_with_weighting do |result, weight| @results.each_with_group do |result, group| @results.each_with_group_and_count do |result, group, count| @results.each_with_count do |result, count| #Any attribute from the result set will work with each_with_blah - so, distances: @result.each_with_geodist do |result, distance|
條件搜尋
這有利於縮小搜尋的範圍,更容易得到我們想要的結果。注意,用作條件的屬性必須被索引(以indexes或has方式)
ThinkingSphinx::Search.search :with => {:tag_ids => 10}ThinkingSphinx::Search.search :with => {:tag_ids => [10,12]}ThinkingSphinx::Search.search :with_all => {:tag_ids => [10,12]}User.search :conditions => {:name => "司徒正美",roles => "admin"}Topic.search :conditions => {:forum_id => 10}Article.search "East Timor", :conditions => {:rating => 3..5}User.search :without => {:roles => "user"} #搜尋所有進階會員,版主與超版。User.search :without_ids => 1萬用字元搜尋
在設定檔sphinx.yml中聲明
development: &my_settings enable_star: 1 min_prefix_len: 0 min_infix_len: 2
嘛,我原來給出的模板就有了,所以大家就不要費心了。
Location.search "*elbourn*" Location.search "elbourn -ustrali", :star => true, :match_mode => :boolean #相當於搜尋*elbourn* -*ustrali* User.search("oo@bar.c", :star => /[\w@.]+/u)#相當於搜尋*oo@bar.c*分組
對時間進行分組。
define_index do # 其他設定 has :created_at end
Fruit.search "apricot", :group_function => :day, :group_by => 'created_at'
一些時間函數
# * :day - YYYYMMDD # * :week - YYYYNNN (NNN is the first day of the week in question, counting from the start of the year ) # * :month - YYYYMM # * :year - YYYY
有了它我們可以很輕鬆地統計每個版本一周內的發貼量
通過模型的某個屬性進行分組
Fruit.search "apricot", :group_function => :attr, :group_by => 'size'
同一個模型使用多個索引
class User :type_ones where "tag_type=0" end define_index do has user_tags, :as=>:type_twos where "tag_type=1" end
http://www.ohloh.net/p/sphinx-for-chinese
http://code.google.com/p/sphinx-for-chinese/
http://www.cnblogs.com/hushixiu/articles/1295605.html
http://www.jedlinski.pl/blog/2008/07/12/thinking-sphinx-as-windows-service/
http://www.expressionlab.com/2008/11/2/thinking-sphinx-on-windows
一些有用的連結http://freelancing-gods.com/posts/a_concise_guide_to_using_thinking_sphinxhttp://railscasts.com/episodes/120-thinking-sphinxhttp://freelancing-god.github.com/ts/en/