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

来源:互联网 发布:微信查看淘宝店铺 编辑:程序博客网 时间:2024/05/18 03:39

 

使用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 [1]: from solango import connection

In [2]: from solango.solr.query import Query

In [3]: q = Query({'facet.field': 'cate_ID'}, q='ad')

In [4]: r = connection.select(q)

In [5]: r.count
Out[5]: 25

In [6]: facet_dict = {}

In [7]: for facet in r.facets:
   ...:     facet_dict[facet.name] = []
   ...:     for value in facet.values:
   ...:         if value.count > 0:
   ...:             facet_dict[facet.name].append({'name': value.name, 'value': value.value, 'count': value.count})
   ...:             
   ...:             

In [8]: facet_dict
Out[8]: 
{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 [9]: 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)
... 
电子电路 [0..12]
电阻 [12..18]
电容 [18..24]

 

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[facet.name] = []
        for value in facet.values:
            if value.count > 0:
                facet_dict[facet.name].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 %}

 

显示效果:

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

 

 

排序其实都是通过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 等等,有兴趣也不妨学习一下。

 

原创粉丝点击