hq8501 发表于 2015-11-12 09:23:40

使用Solr快速实现Django的全文搜索[转]

  

使用Solr快速实现Django的全文搜索。http://fzuslideblog.appspot.com/2010/03/25/django_solr_search.html原文地址
  Django本身并没有提供全文搜索的功能,而自己给Django添加全文搜索的功能选择也有很多,可以用Sphinx,Lucene,Xapian等等来做。这里我们选用基于Lucene的全文搜索服务器Solr来快速的搭建Django的全文搜索应用。
  需要用到的开源项目:
  Solr:http://lucene.apache.org/solr/ (Apache License)
  Django-solr-search: http://code.google.com/p/django-solr-search/( BSD L)
  Pymmseg-cpp: http://code.google.com/p/pymmseg-cpp/ (MIT L)
  Django-solr-search是用来连接Solr服务器的Django插件,Py mmseg-cpp是Python封装的mmseg中文分词模块。
  1.配置环境
  首先安装Solr, 下载下来直接解压,然后进入apache-solr-1.4.0/example下面,直接运行:
  java -jar start.jar
  然后查看http://localhost:8983/solr/,看到欢迎界面,一切ok。
  然后把Django-solr-search下载下来,可以选择直接setup,也可以直接把其中 的solango放入需要增加全文搜索的Project下面。
  然后修改settings.py,加入solango的app,如下:

  INSTALLED_APPS = {
            ...
            'solango',
            ...
      }
  

添加Solr的路径:
  SOLR_ROOT ='path/apache-solr-1.4.0/example/'
SOLR_SCHEMA_PATH = SOLR_ROOT + 'solr/conf/schema.xml'
SOLR_DATA_DIR = SOLR_ROOT + 'solr/data'
SOLR_DEFAULT_OPERATOR = 'or'
  最后在项目下运行命令:
  让django启动Solr服务器 python manage.py solr --start
  查看是否连接成功:
  python manage.py shell

  >>> import solango
>>> solango.connection.is_available()
      True
  显示True,环境就搭好了。
  
  2.定义Document模型
  先看我们需要全文搜索的model:

  class ElectronicComponent(models.Model):
    '''
    Electronic Components Products
    '''
    p_name = models.CharField(_('product name'),max_length=200,
            help_text=_("Example:可调式电容"))
    partno = models.CharField(_('part number'),max_length=200,
            help_text=_("Alphanumeric characters only (letters, digits and underscores)"))
    dc = models.CharField(_('date code'),max_length=10,default=' ',
            help_text=_("digits and '+','-','/'only "))
    qty = models.IntegerField(_('quantity'),null=True,blank=True,
            help_text=_("digits only"))
    mfg = models.CharField(_('manufactory'),max_length=200,default=' ',
            help_text=_("manufactory"))
    pack = models.CharField(_('packaging'),max_length=20,default=' ',
            help_text=_("packaging"))
    desp = models.TextField(_('description'),default=' ',
            help_text=_("description"))
    date_update = models.DateTimeField(_('last update'), default=datetime.datetime.now)   
    special_attrib = models.ManyToManyField('BasicAttribValue')
    cate_ID = models.ForeignKey('ElecompCategory')
    u = models.ForeignKey(IcUser)
    def __unicode__(self):
      return self.partno
  
  根据上面的model,定义全文搜索的Document模型:

  from models import ElectronicComponent
import solango
class ElectronicComponentDocument(solango.SearchDocument):
    '''
    非IC全文搜索模型
    '''
    p_name = solango.fields.CharField(copy=True)
    partno = solango.fields.CharField(copy=True)
    mfg = solango.fields.CharField(copy=True)
    pack = solango.fields.CharField(copy=True)
    desp = solango.fields.CharField(copy=True)
    cate_ID = solango.fields.CharField(copy=True)
    user = solango.fields.CharField(copy=True)
    def transform_user(self, instance):
      return instance.u
solango.register(ElectronicComponent, ElectronicComponentDocument)
  
  根据原有的ElectronicComponent生成ElectronicComponentDocument全文搜索模型,后面transform_user函数返回自己想给user返回的值。
  3.建立索引
  定义好以上的模型后,就可以建立索引了,先查看一下刚才定义的fields:
  运行命令:python manage.py solr --fields
  

  ########## FIELDS ###########
  
  <field name="mfg" type="string" indexed="true" stored="true" omitNorms="false" required="false" multiValued="false"/>
  <field name="url" type="string" indexed="true" stored="true" omitNorms="false" required="false" multiValued="false"/>
  <field name="text" type="text" indexed="true" stored="true" omitNorms="false" required="false" multiValued="true"/>
  <field name="site_id" type="integer" indexed="true" stored="true" omitNorms="false" required="true" multiValued="false"/>
  <field name="desp" type="string" indexed="true" stored="true" omitNorms="false" required="false" multiValued="false"/>
  <field name="partno" type="string" indexed="true" stored="true" omitNorms="false" required="false" multiValued="false"/>
  <field name="cate_ID" type="string" indexed="true" stored="true" omitNorms="false" required="false" multiValued="false"/>
  <field name="user" type="string" indexed="true" stored="true" omitNorms="false" required="false" multiValued="false"/>
  <field name="model" type="string" indexed="true" stored="true" omitNorms="false" required="true" multiValued="false"/>
  <field name="p_name" type="string" indexed="true" stored="true" omitNorms="false" required="false" multiValued="false"/>
  <field name="id" type="string" indexed="true" stored="true" omitNorms="false" required="true" multiValued="false"/>
  <field name="pack" type="string" indexed="true" stored="true" omitNorms="false" required="false" multiValued="false"/>
  
  ######## COPY FIELDS ########
  
  <copyField source="mfg" dest="text"/>
  <copyField source="desp" dest="text"/>
  <copyField source="partno" dest="text"/>
  <copyField source="cate_ID" dest="text"/>
  <copyField source="user" dest="text"/>
  <copyField source="p_name" dest="text"/>
  <copyField source="pack" dest="text"/>
  
  
  以上就是刚才模型中定义的Fields,如果以前已经有旧的索引存在了,就先运行:
  python manage.py solr –-flush
  把旧索引清空。然后根据我们定义的Document模型同步Schema.xml:
  
  python manage.py solr --schema
  最后重新启动Solr就可建立索引:
  python manage.py solr --reindex
  
  4.查询与分词
  能够简便的实现facets查询,这也是选择Solr来做全文搜索服务器原因之一。
  对刚才建立的索引,做一个测试:

  In : from solango import connection
In : from solango.solr.query import Query
In : q = Query({'facet.field': 'cate_ID'}, q='ad')
In : r = connection.select(q)
In : r.count
Out: 25
In : facet_dict = {}
In : for facet in r.facets:
   ...:   facet_dict = []
   ...:   for value in facet.values:
   ...:         if value.count > 0:
   ...:             facet_dict.append({'name': value.name, 'value': value.value, 'count': value.count})
   ...:            
   ...:            
In : facet_dict
Out:
{u'cate_ID': [{'count': 16,
               'name': u'/u6307/u793a/u706f',
               'value': u'/u6307/u793a/u706f'},
            {'count': 4,
               'name': u'/u5176/u4ed6/u4e94/u91d1/u3001/u5de5/u5177',
               'value': u'/u5176/u4ed6/u4e94/u91d1/u3001/u5de5/u5177'},
            {'count': 3,
               'name': u'/u5355/u7247/u673aMcu',
               'value': u'/u5355/u7247/u673aMCU'},
            {'count': 1,
               'name': u'/u5e72/u7c27/u7ba1',
               'value': u'/u5e72/u7c27/u7ba1'},
            {'count': 1,
               'name': u'/u793a/u6ce2/u5668',
               'value': u'/u793a/u6ce2/u5668'}],
u'model': [{'count': 25,
             'name': u'Electroniccomponent',
             'value': u'product__electroniccomponent'}]}
In : for one in facet_dict['cate_ID']:
            print one['name'], one['count']
   ....:   
   ....:   
  
指示灯 16
其他五金、工具 4
单片机Mcu 3
干簧管 1
示波器 1
  
  上面给facet增加一个新的field:cate_ID(分类 ),以“ad”作为关键字来查询,可以看到共检索出25条数据,同时也得到了这些数据位于5种不同分类及分别的数量。
  利用facet进行查询,对于分类搜索提供了一个行之有效的解决方案。
  定义更多种类的Query来实现搜索,基本上就跟Django没有关系,完全看对Solr的操作了。
  至于分词,可以独立出来看,而且可以选择的方式方法很多,这里拿Pymmseg举个例子:
  

  >>> from pymmseg import mmseg
>>> mmseg.dict_load_defaults()
>>> text = '电子电路电阻电容'
>>> algor = mmseg.Algorithm(text)
>>> for tok in algor:
...   print '%s [%d..%d]' % (tok.text, tok.start, tok.end)
...
电子电路
电阻
电容
  
  5.模板显示与排序
  对于模板的显示,Django-solr-search也提供了简洁的方式,在定义Document模型的之后加入对应的模板,来看个官方的例子。
  在定义模型的时候指定Document对应的模板:

  class EntryDocument(solango.SearchDocument):
    ... Fields ...
  
    class Media:
      template = "coltrane/entry_document.html"
    ... Transforms ...
solango.register(Entry, EntryDocument)
  
  模板:

  <div class="searchdocument">
<h3>Entry: <a href="{{document.fields.url.value }}" >{{ document.fields.title.value }}</a> </h3>
<p>
{{ document.highlight|safe }}
</p>
<ul class="sublinks">
    <li>at {{ document.fields.date.value|date:"N j Y" }}</li>
    <li class="last"><a href="{{document.fields.url.value }}">permalink</a></li>
</ul>
</div>
  
  在其他的页面中使用render_html方法, 引用嵌入定义的Document模板

  {% for doc in paginator.results.documents %}
    {{doc.render_html|safe}}
{% endfor %}
  
  当然,你也可以像以前一样使用普通的方式渲染模板。
  后台代码:

  def solr_search(request):
    '''
    用solr进行全文搜索
    '''
    keywords = request.GET['q']
    q = Query({'facet.field': 'cate_ID'},q=keywords)
    r = solr_conn.select(q)
    facet_dict = {}
    for facet in r.facets:
      facet_dict = []
      for value in facet.values:
            if value.count > 0:
                facet_dict.append({'name': value.name, 'value': value.value, 'count': value.count})
    return render_to_response('product/productlist_solr.html',
                {'documents': r.documents,
               'cate_ID_dict': facet_dict['cate_ID'],
               'keyword': keywords},
                context_instance=RequestContext(request))
  
  模板中引用:

  {% for cate_ID in cate_ID_dict %}
          <td><a href="/product/category/{{cate_ID.name}}.html">{{cate_ID.name}}({{cate_ID.count}})</a></td>
          {% endfor %}
{% autoescape off %}
      {% for one in documents %}
      <tr class="{% cycle row1,row2 %}">
          <td><input type="checkbox" name="checkone" value="{{one.id}}" /></td>
          <td align="left" class="iclist"><a href="/productmenu/{{one.id}}.html"><spanclass="partno">{{one.partno}}</span></a></td>
          <td>{{one.mfg}}</td>
          <td>{{one.desp}}</td>
          <td class="red"><a href="/inquire/ask_attach/?partno={{one.id}}">{% trans 'Inquiry' %}</a></td>
      </tr>
      {% endfor %}
{% endautoescape %}
  
  显示效果:
http://farm3.static.flickr.com/2792/4461259733_c696dab350_o.jpg
  
  
  排序其实都是通过Solr来控制的,在settings.py中加入下面代码来控制排序:


SEARCH_SORT_PARAMS = {
      "score desc": "Relevance",
      "date desc" : "Date" # Added date
}
  
  6.索引的更新
  索引的更新问题对于一个应用来说也至关重要。这里提供了两种基本的更新方法。
  ImmediateIndexer:
  这个是默认的即时更新,不需要任何设置。但是lucene的索引创建速度并不快,因此这种方法只适合在数据量比较小,数据写入和删除操作并不多的情况下使用。
  DBQueuedIndexer
  这个方法是通过在数据库中创建一张表,记录下来数据操作的队列,然后在指定的时间针对这个队列表里的数据进行更新操作。
  需要在settings.py加入:



SEARCH_INDEXER = "solango.indexing.DBQueuedIndexer"
  定时运行:
  python manage.py solr –index-queued
  回过头来再看一下Django使用Solr搜索的原理,并不复杂,就是一个完整的爬虫类:
  通过 urllib2对Solr服务器的url进行访问,再读取Solr返回的xml文本,对文本解析,封装,最后渲染模板。
  以上就是快速的给Django应用加入全文搜索的一个方法,主要还是Django-solr-search的使用。而这个插件应用背景也比较有趣,最初做给是给《华盛顿时报》的网站使用的,这家报纸从创建之初竟然连续20年从未实现过盈利。
  
  官方文档地址:http://www.screeley.com/djangosolr/index.html
  
  除了 Django-solr-search之外,还有很多第三方的全文搜索插件可以使用,例如Haystack 等等,有兴趣也不妨学习一下。
  
   
版权声明:本文为博主原创文章,未经博主允许不得转载。
页: [1]
查看完整版本: 使用Solr快速实现Django的全文搜索[转]