Django Haystack 全文检索与关键词高亮的实现
作者:HelloGitHub-追梦人物
文中所涉及的示例代码,已同步更新到HelloGitHub-Team仓库
博客提供RSS订阅应该是标配,这样读者就可以通过一些聚合阅读工具订阅你的博客,时时查看是否有文章更新,而不必每次都跳转到博客上来查看。现在我们就来为博客添加RSS订阅功能。
在此之前我们使用了Django内置的一些方法实现了一个简单的搜索功能。但这个搜索功能实在过于简单,没有多大的实用性。对于一个搜索引擎来说,至少应该能够根据用户的搜索关键词对搜索结果进行排序以及高亮关键字。现在我们就来使用django-haystack实现这些特性。
DjangoHaystack简介
django-haystack是一个专门提供搜索功能的django第三方应用,它支持Solr、Elasticsearch、Whoosh、Xapian等多种搜索引擎,上一版本的教程中我们使用Whoosh加jieba中文分词的方案,原因是为了简单,无需安装外部服务。但现在有了docker,安装一个外部服务就是轻而易举的事情,所以这次我们采用更为强大的elasticsearch作为我们博客的搜索引擎,同时使用elasticsearch的中文分词插件ik,来提升中文搜索的效果。
安装必要依赖
安装django-haystack
django-haystack安装非常简单,只需要执行pipenvinstalldjango-haystack即可。需要注意的是,目前elasticsearch有2系列和5系列两大版本,本来新项目的原则是尽可能采用新版本,但目前django-haystack在pypi上发布的稳定版只支持elasticsearch2,master分支下支持elasticsearch5,因此处于稳定性考虑,我们暂时使用elasticsearch2,后续如果django-haystack发布了支持elasticsearch5的pypi版本,我们会升级到elasticsearch5,有了docker,升级就是轻而易举的事情。
由于使用elasticsearch服务,haystack连接elasticsearch需要python版本的SDK支持,因此还需要安装elasticsearchpythonSDK,这里我们不要直接使用pipenv安装,而是手动编辑Pipfile文件,指定SDK的版本,否则pipenv默认会安装最新版。打开Pipfile文件,将依赖手动添加到packages板块下:
[packages] django="~=2.2" elasticsearch=">=2,<3"
安装elasticsearch2
接下来就是构建一个新的容器来运行elasticsearch服务,因此首先需要来编排容器镜像,回顾一下容器镜像的目录结构:
compose\ local\ production\ django\ nginx\
由于elasticsearch在线上环境和本地测试都要使用,我们把镜像编排在production目录下,新建一个elasticsearch目录,用来存放和elasticsearch相关的内容。Dockfile内容如下:
FROMelasticsearch:2.4.6-alpine COPY./compose/production/elasticsearch/elasticsearch-analysis-ik-1.10.6.zip/usr/share/elasticsearch/plugins/ RUNcd/usr/share/elasticsearch/plugins/&&mkdirik&&unzipelasticsearch-analysis-ik-1.10.6.zip-dik/ RUNrm/usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-1.10.6.zip USERroot COPY./compose/production/elasticsearch/elasticsearch.yml/usr/share/elasticsearch/config/ RUNchownelasticsearch:elasticsearch/usr/share/elasticsearch/config/elasticsearch.yml USERelasticsearch
这个镜像从elasticsearch的官方基础镜像2.4.6版本进行构建,接着我们把ik分词插件复制到elasticsearch安装插件的目录下,然后解压启用。
接着我们又把elasticsearch.yml配置文件复制到容器内,然后切换用户为elasticsearch,因为我们将以elasticsearch用户和组运行elasticsearch服务。
elasticsearch.yml配置文件内容很简单:
bootstrap.memory_lock:true network.host:0.0.0.0
其中bootstrap.memory_lock这个参数是为了提高elasticsearch的效率(涉及到JVM相关的优化,不做过多介绍)。network.host指定服务启动的地址。
接着修改dockercompose文件,我们先在本地启动,因此修改local.yml文件,加入elasticsearch服务:
version:'3' volumes: database_local: esdata_local: services: hellodjango_blog_tutorial_local: #其它配置不变... depends_on: -elasticsearch_local elasticsearch_local: build: context:. dockerfile:./compose/production/elasticsearch/Dockerfile image:elasticsearch_local container_name:elasticsearch_local volumes: -esdata_local:/usr/share/elasticsearch/data ports: -"9200:9200" environment: -"ES_JAVA_OPTS=-Xms512m-Xmx512m" ulimits: memlock: soft:-1 hard:-1 nproc:65536 nofile: soft:65536 hard:65536
主要是加入了elasticsearch服务,其中environment和ulimits的参数与elasticksearch服务调优有关,对于简单的博客搜索来说,调优的意义不是很大,因此这里不做过多介绍,感兴趣的可以参考elasticksearch的官方文档。
配置Haystack
安装好djangohaystack后需要在项目的settings.py做一些简单的配置。
首先是把djangohaystack加入到INSTALLED_APPS设置里:
blogproject/settings.py
INSTALLED_APPS=[ 'django.contrib.admin', #其它app... 'haystack', 'blog', 'comments', ]
然后加入如下配置项:
blogproject/common.py
#搜索设置 HAYSTACK_CONNECTIONS={ 'default':{ 'ENGINE':'haystack.elasticsearch2_backend.Elasticsearch2SearchEngine', 'URL':'', 'INDEX_NAME':'hellodjango_blog_tutorial', }, } HAYSTACK_SEARCH_RESULTS_PER_PAGE=10 HAYSTACK_SIGNAL_PROCESSOR='haystack.signals.RealtimeSignalProcessor'
HAYSTACK_CONNECTIONS的ENGINE指定了djangohaystack使用的搜索引擎,这里我们使用了haystack默认的Elasticsearch2搜索引擎。PATH指定了索引文件需要存放的位置,我们设置为项目根目录BASE_DIR下的whoosh_index文件夹(在建立索引是会自动创建)。
HAYSTACK_SEARCH_RESULTS_PER_PAGE指定如何对搜索结果分页,这里设置为每10项结果为一页。
HAYSTACK_SIGNAL_PROCESSOR指定什么时候更新索引,这里我们使用haystack.signals.RealtimeSignalProcessor,作用是每当有文章更新时就更新索引。由于博客文章更新不会太频繁,因此实时更新没有问题。
由于开发环境和线上环境,elasticsearch服务的url地址是不同的,所以我们在common的配置中没有指定url,在local.py设置文件指定之:
HAYSTACK_CONNECTIONS['default']['URL']='http://elasticsearch_local:9200/'
处理数据
接下来就要告诉djangohaystack使用哪些数据建立索引以及如何存放索引。如果要对blog应用下的数据进行全文检索,做法是在blog应用下建立一个search_indexes.py文件,写上如下代码:
blog/search_indexes.py
fromhaystackimportindexes from.modelsimportPost classPostIndex(indexes.SearchIndex,indexes.Indexable): text=indexes.CharField(document=True,use_template=True) defget_model(self): returnPost defindex_queryset(self,using=None): returnself.get_model().objects.all()
这是djangohaystack的规定。要相对某个app下的数据进行全文检索,就要在该app下创建一个search_indexes.py文件,然后创建一个XXIndex类(XX为含有被检索数据的模型,如这里的Post),并且继承SearchIndex和Indexable。
为什么要创建索引?索引就像是一本书的目录,可以为读者提供更快速的导航与查找。在这里也是同样的道理,当数据量非常大的时候,若要从这些数据里找出所有的满足搜索条件的几乎是不太可能的,将会给服务器带来极大的负担。所以我们需要为指定的数据添加一个索引(目录),在这里是为Post创建一个索引,索引的实现细节是我们不需要关心的,我们只关心为哪些字段创建索引,如何指定。
每个索引里面必须有且只能有一个字段为document=True,这代表djangohaystack和搜索引擎将使用此字段的内容作为索引进行检索(primaryfield)。注意,如果使用一个字段设置了document=True,则一般约定此字段名为text,这是在SearchIndex类里面一贯的命名,以防止后台混乱,当然名字你也可以随便改,不过不建议改。
并且,haystack提供了use_template=True在text字段中,这样就允许我们使用数据模板去建立搜索引擎索引的文件,说得通俗点就是索引里面需要存放一些什么东西,例如Post的title字段,这样我们可以通过title内容来检索Post数据了。举个例子,假如你搜索Python,那么就可以检索出title中含有Python的Post了,怎么样是不是很简单?数据模板的路径为templates/search/indexes/youapp/
templates/search/indexes/blog/post_text.txt {{object.title}} {{object.body}}
这个数据模板的作用是对Post.title、Post.body这两个字段建立索引,当检索的时候会对这两个字段做全文检索匹配,然后将匹配的结果排序后作为搜索结果返回。
配置URL
接下来就是配置URL,搜索的视图函数和URL模式djangohaystack都已经帮我们写好了,只需要项目的urls.py中包含它:
blogproject/urls.py
urlpatterns=[ #其它... path('search/',include('haystack.urls')), ]
另外在此之前我们也为自己写的搜索视图配置了URL,把那个URL删掉,以免冲突:
blog/urls.py
#path('search/',views.search,name='search'),
修改搜索表单
修改一下搜索表单,让它提交数据到djangohaystack搜索视图对应的URL:
主要是把表单的action属性改为{%url'haystack_search'%}
创建搜索结果页面
haystack_search视图函数会将搜索结果传递给模板search/search.html,因此创建这个模板文件,对搜索结果进行渲染:
templates/search/search.html
{%extends'base.html'%} {%loadhighlight%} {%blockmain%} {%ifquery%} {%forresultinpage.object_list%}{%highlightresult.object.titlewithquery%} {{result.object.category.name}} {{result.object.created_time}} {{result.object.author}} {{result.object.comment_set.count}}评论 {{result.object.views}}阅读
{%highlightresult.object.bodywithquery%}