利用thinking sphinx實現全文檢索索引

來源:互聯網
上載者:User

隨便抄幾段介紹一下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/

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.