前言
在使用Django搭建博客的过程中,必不可少的需要博文搜索功能。读者输入关键此,然后根据关键词搜索出对应的博客内容。新手可能上来就直接使用数据库模糊查询了,然而事实上数据库的模糊查询效率低,博客文章多的时候就能很明显的感受到。而且模糊查询不支持将用户输入的内容进行分词检索,不支持关键词高亮等等。反正就是各种不好吧,哈哈哈🤦。所以就要用到搜索引擎,下面就介绍使用haystack,Whoosh,Jieba打造高效,可分词,并且支持中文分词,可自定义搜索,可多条件搜索,可关键词高亮的全文搜索功能。
Django Haystack Whoosh Jieba简介
django-haystack:是一个专门提供搜索功能的djang 第三方库,支持Solr、Elasticsearch、Whoosh、Xapian等多种搜索引擎。
whoosh:是一个纯Python编写的全文搜索引擎。虽然性能比不上sphinx、xapian、Elasticsearc等,但是无二进制包,程序不会莫名其妙的崩溃。而且比较小巧,安装后仅2.61M。同时配置简单方便,非常容易集成到django/python里面。对于小型的站点,whoosh已经足够使用。
jieba:是一款免费的中文分词包,用于替换Whoosh自带的英文分词(英文分词对中文支持不太友好)。
必备环境安装
在虚拟环境中依次安装:django-haystack,Whoosh,Jieba
pip install django-haystack
pip install whoosh
pip install jieba
安装如果报错:python setup.py egg_info Check,先安装setuptools_scm,再安装django-haystack
pip install setuptools_scm
配置Haystack
这里以博客中的blog应用配置Haystack为例,演示完整的配置过程。首先在settings.py中添加haystack应用。
INSTALLED_APPS = (
...
'blog',
'haystack',
)
对应的示例blog的models如下:
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User
class Note(models.Model):
user = models.ForeignKey(User)
pub_date = models.DateTimeField()
title = models.CharField(max_length=200)
body = models.TextField()
def __str__(self):
return self.title
在blog应用目录下,添加一个索引文件blog/search_indexes.py
from haystack import indexes
from blog.models import Note
# 修改此处,类名为模型类的名称+Index,比如模型类为Note,则这里类名为NoteIndex
class NoteIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
# 修改此处,为你自己的model
return Note
def index_queryset(self, using=None):
"""Used when the entire index for model is updated."""
return self.get_model().objects.all()
为什么要创建索引?索引就像是一本书的目录,可以为读者提供更快速的导航与查找。在这里也是同样的道理,当数据量非常大的时候,若要从这些数据里找出所有的满足搜索条件的几乎是不太可能的,将会给服务器带来极大的负担,所以我们需要为指定的数据添加一个索引(目录)。
并且django haystack规定。要相对某个app下的数据进行全文检索,就要在该app下创建一个search_indexes.py文件。然后创建一个XXIndex 类(XX 为含有被检索数据的模型,如这里的 Note),并且继承 SearchIndex
和 Indexable。
然后每个索引里面必须有且只能有一个字段为document=True
,这代表django haystack和搜索引擎将使用此字段的内容作为索引进行检索(primary field)。注意,如果使用一个字段设置了document=True
,则一般约定此字段名为text
,这是在 SearchIndex
类里面一贯的命名,以防止后台混乱,当然名字你也可以随便改,不过不建议改。
并且,haystack 提供了use_template=True 在 text 字段中,这样就允许我们使用数据模板去建立搜索引擎索引的文件,说得通俗点就是索引里面需要存放一些什么东西,例如Note的title字段,这样我们可以通过title内容来检索Note数据了。这里以blog为例,数据模板的路径为 templates/search/indexes/blog/note_text.txt,并在其中添加要搜索的字段:
{{ object.title }}
{{ object.body }}
接下来就是配置URL,搜索的视图函数和URL模式django haystack都已经帮我们写好了,只需要项目的urls.py中包含它:
urlpatterns = [
url(r'^search/', include('haystack.urls')),
]
haystack_search
视图函数会将搜索结果传递给模板 search/search.html,因此创建这个模板文件,对搜索结果进行渲染:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
这是搜索结果:
{% if query %}
<h3>搜索结果如下:</h3>
{% for result in page.object_list %}
<a href="{{ result.object.get_absolute_url }}">{{ result.object.title }}</a><br/>
{% empty %}
<p>啥也没找到</p>
{% endfor %}
{% if page.has_previous or page.has_next %}
<div>
{% if page.has_previous %}<a href="?q={{ query }}&page={{ page.previous_page_number }}">{% endif %}« 上一页{% if page.has_previous %}</a>{% endif %}
|
{% if page.has_next %}<a href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}下一页 »{% if page.has_next %}</a>{% endif %}
</div>
{% endif %}
{% endif %}
<a href="/">重新搜索<a>
</body>
</html>
其中query:搜索的字符串,page:当前页的page对象,paginator:分页paginator对象。haystack自带分页,每页显示数量在settings.py可以设置:
# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 指定搜索结果每页显示多少条信息
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 13
注意:
- 一定要配置HAYSTACK_SIGNAL_PROCESSOR这一项,否则我们新增/修改/删除文章时,不会自动生成索引,索引不更新,我们就搜不到对应的内容。
- 在模板中使用时,需要使用xxx.object.xxx的方式来获取数据,和Django原生的可能有一点区别。
同步数据库,并第一次手动生成索引后,就可以正常搜索使用了。
python manager.py makemigrations
python migrate
# 手动生成索引
python manage.py rebuild_index
注意:如果django3.x 使用haystack 报错:ImportError: cannot import name 'six' from 'django.utils'。请按照下面步骤操作即可:
# 第一步
pip3 install six
# 第二步
cd #进入家目录
cd .local/lib/python3.6/site-packages
cp six.py django/utils #将six.py拷贝进django/utils/目录下
# 第三步
# 将site-packages/haystack/inputs.py 中
from django.utils.encoding import force_text, python_2_unicode_compatible
# 改为
from django.utils.encoding import force_text
from django.utils.six import python_2_unicode_compatible
# 问题解决
修改Whoosh搜索引擎为中文分词
将 haystack/backends/whoosh_backends.py文件拷贝到blog目录下,重命名为whoosh_cn_backends.py,然后修改:whoosh_cn_backend.py
#在顶部添加
from jieba.analyse import ChineseAnalyzer
# 搜索`schema_fields[field_class.index_fieldname] = TEXT`,改成如下:
schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=ChineseAnalyzer(),field_boost=field_class.boost, sortable=True)
并在settings.py中添加如下配置:
# 全文检索框架配置
HAYSTACK_CONNECTIONS = {
'default': {
# 修改后的whoosh引擎,支持中文分词
'ENGINE': 'blog.whoosh_cn_backend.WhooshEngine',
# 索引文件路径
'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
}
}
# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 指定搜索结果每页显示多少条信息
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 6
然后同步数据库,重新生成索引即可。
python manager.py makemigrations
python migrate
# 手动生成索引
python manage.py rebuild_index
高亮搜索关键词
haystack为我们提供了{% highlight %} 和 {% more_like_this %}2个标签,可以让匹配的关键字显示高亮。
{% highlight <text_block> with <query> [css_class "class_name"] [html_tag "span"] [max_length 200] %}
此示例中的语法大概意思就是:我们为<text_block>中所有query的内容制定了一个span标签,标签的class设置为class_name,并限制<text_block>被高亮处理后的长度为200。
然后我们就可以通过css去控制class_name的高亮颜色等等样式了,还是很方便的。
重写搜索方法,实现自定义搜索
如果直接采用上面的配置进行搜索,可能有时候满足不了用户的复杂需求,比如:对搜索结果按照时间、按照文章分类等进行过滤。这里我看了haystack的源码后,发现搜搜其实是通过SearchQuerySet这个方法实现的。所以我们只需要自定义一个方法,对SearchQuerySet方法的搜索结果进行相应的操作(各种过滤都没问题),然后通过自定义的模板进行渲染即可。
from haystack.query import SearchQuerySet
def search(request):
query = request.GET.get('q', '')
# 这里可以随便过滤
note = SearchQuerySet().filter(body=query, pub_date...)
return render(request, 'search.html', {'query': query, 'note': note,)