Rails 中的全文搜索

jopen 10年前

介绍

在Web应用中,搜索数据记录是一个常见的需求。最常见的一个需求就是允许用户从大量的数据记录中快速访问他们想要的数据。虽然可以使用简单的SQL查询应对这样的查询需求,但有时,更有效的是使用搜索引擎。

Solr是Apache Lucene项目中的一个流行的搜索平台。其主要功能包括强大的全文搜索,点击显示,分面搜索,近实时索引,动态聚类,数据库集成,富文本处理和空间搜索。在本教程中,我们将寻求利用Sunspot执行全文本搜索,Sunspot是一个能够使Solr集成在ruby应用中的类库。

项目安装

我在 Github 创建了一个简单的app, 我将用在这里而不是开始一个全新的项目。这个 app 显示了一个产品列表,包括产品名,图片,价格和描述。我引用了一些 seed 数据,所以如果你不想自己输入数据的话, 你可以运行 rake db:seed  。这个应用程序使用了Paperclip处理图片附件,因为我用到图片大小调整,所以在你的系统需要装上ImageMagick。随教程的进行你还需要在你的机器安装Java运行环境。

下面图片展示了这个应用程序。顶部的搜索表单现在还没做什么,但是我们将使得一个用户通过搜索产品并得到基于不仅仅是产品名,也包括它的商品描述的结果。

Rails 中的全文搜索

搜索

首先我们要将Sunspot和Solr引入到我们的依赖库中. 在开发中, 我们会使用预打包有Solr发行版的sunspot_solr依赖包, 这样我们就不需要单独安装它了.

gem 'sunspot_rails'     group :development do      gem 'sunspot_solr'  end

运行 bundle install 生成Sunspot配置文件.

 rails generate sunspot_rails:install

这里创建了文件 /config/sunspot.yml 用以让你的应用知道在哪里找到 Solr 服务器.

建立为你想要索引的对象, 并为其添加searchable块. 在starter project中, 我们有一个含有name, price, description 和 photo字段的Product模型. 我们要为字段 name 和description 添加全文搜索. 在/models/product.rb中添加:

searchable do      text :name, :description  end

通过执行下面的语句启动Solr服务器:

rake sunspot:solr:start

Sunspot 索引你添加的新记录 , 但是如果数据库中已经有了许多数据, 执行 rake sunspot:reindex 来索引他们.

然后我们将代码放入 Products 控制器中,它将会得到用户的输入并将其传入搜索引擎. 在下面的代码中,我们在一个Product模型上面调用搜索并传入一个块. 我们在这个块中调用 fulltext 方法并传入我们想要搜索的查询字符串. 这里我们可以使用到几个方法来定制我们想要的搜索结果. 然后搜索结果就会被赋值给 @products,它将会作用于我们的视图.

def index      @query = Product.search do          fulltext params[:search]      end      @products = @query.results  end

运行应用程序,你就应当能够用这个程序来搜索可用的产品了. 

Solr 将会使用输入的关键词或者短句对产品的名称和描述做一个大小写敏感的搜索. 你可以通过让某个域拥有比其它域更多的权重来提升搜索结果的相关性. 这是由 boost 方法来 做到的,它会被传入一个值来决定不同域的权重. 带有最大值的域将取得更多的重视. 

在我们的应用程序中,我们可以让在名称中搜索到关键字符串的产品设置一个较高的分数. 我们可以通过在/models/product.rb中进行如下改变来做到.

searchable do      text :name, :boost => 2      text :description  end

使用 rake sunspot:reindex 可以对记录进行重新索引,而现在在产品名称中搜索到关键字的产品将会比在产品描述中搜索到关键字的产品排名要高. 你可以添加更多的记录来测试这一点.

切片浏览

切片浏览是一种通过将相关属性进行不同组合的方式来浏览查询数据. 例如, 在我们的应用程序中,我们可以依据价格级别来分类搜索产品,并给出每一个级别的总数.

首先将价格添加到 /models/product.rb 中的searchable方法中

searchable do      text :name, :boost => 2      text :description      double :price  end

然后在控制器中调用 facet . 产品就会根据按 $100.00 为间隔的价格级别来切片 . 这里我们假定所有的产品价钱都低于 $500.

def index      @query = Product.search do          fulltext params[:search]             facet :price, :range => 0..500, :range_interval => 100          with(:price, Range.new(*params[:price_range].split("..").map(&:to_i))) if params[:price_range].present?         end      @products = @query.results  end

在视图文件中,将下面的代码粘贴到你想要看到切片结果的地方.

<div class="row">      <h3>Search Results</h3>      <ul>          <% for row in @query.facet(:price).rows %>              <li>                  <% if params[:price_range].blank? %>                      <%= link_to row.value, :price_range => row.value, :search => params[:search] %> (<%= row.count %>)                  <% else %>                      <%= row.value %> (<%= link_to "X", :price_range => nil %>)                  <% end %>              </li>          <% end %>      </ul>  </div>

现在,当你搜索一个条目时,将会有一个切片列表展示出在每个价格级别会有多少条结果 . 在我们的示例应用程序中,如果你搜索关键词 'camera', 你将会看到下面这份列表.

100.0..200.0 (2)  200.0..300.0 (1)  300.0..400.0 (1)

每一项都是一个链接,并且当在上面点击的时候,你将会获得一个满足你的搜索条件,并且其价格也会落在你所点击的相应区间的产品列表. 

链接向index动作传入了原有的查询关键词,以及点击所选择的价格区间 . 由于其传入的价格区间是一个字符串,我们要使用 Range.new(*params[:price_range].split("..").map(&:to_i)) 来将其转换回区间. 你可以使用条件语句来输出更多对用户友好的连接,比如像$100 - $199 (2) 而不是 100.0..200.0 (2) ,但这里我们不会深入讨论这个.

高级的配置

你可以使用更多的配置来定制Solr的运作 . 在其默认的配置中, Sunspot 通过使用一个智能的叫做StandardTokenizer的标记生成器基于空格和其它分隔符将搜索字符串分成多个关键词标记 . 然后这些关键词标记将会被转换成小写并被提取出搜索的关键词.

这有时可能已经够可以的了,但你也许还需要对搜索引擎进行配置,以容许人工错误或不太严谨的查询. 比如,你可能想要要向引擎提供一些同义词,那样当用户并没有输入匹配你记录中的精确文本时,他们仍然能得到一个类似的结果. 一个例子就是你可能在记录中有标记为 'ipod' 的数据项. 你可能会提供想 'iPod', 'i-pod' 和 'i pod' 的同义词,来增加用户找到数据的可能性.

另外一个你可以添加的实用功能是词干搜索, 其将允许Solr实用相同的词干匹配不同的关键词. 例如,如果用户输入了 'run', 他们会得到带有 'run' 和 'running'的结果. 或者如果他们搜索'walk', 结果将会包含含有 'walk', 'walking', 'walked', 等等关键词的数据.

Solr 的设置可以在 solr/conf/schema.xml 中找到,这个文件可以修改用来改变服务器的配置. 这超出了本教程的范围,而作为对此的更深入介绍,请查看 全文搜索的高级配置 以及 Solr wiki.

结论

现在来总结一下,通过执行下面的命令停止Solr服务:

rake sunspot:solr:stop

我们已经搞了一把借助Sunspot gem来整合Solr搜索引擎到Rails应用,除了那些我们已经固定的设置外,还有很多搜索的设置是可以个性化设置的,大家可以通过阅读Readme File找到更多选项来玩玩。

Solr给你提供了一种通过传统SQL语句没法达成的搜索能力。对于那些简单的应用,数据库记录也很少,通过SQL来搜索是没有性能瓶颈的,但如果你想要灵活升级,用Solr搜索引擎或同类玩意来替代它是值得的。