searcher django基于haystack,Whoosh,Jieba的中文全文检索demo 由于项目需求的原因,通过自定义my_searchview.py的方式,将检索结果通过提取出来转换为json的方式返回给中间层或前端。
1、安装相关包
Whoosh: whoosh是一个纯python实现的全文搜索组件,是原生唯一的python写的全文搜索引擎,虽然有说whoosh性能比不上sphinx,xapian等。不过whoosh本身很小,安装后才2.61M,非常容易集成到django/python里面 whoosh主要特性:
敏捷的API(Pythonic API)。
纯python实现,无二进制包。程序不会莫名其妙的崩溃。
按字段进行索引。
索引和搜索都非常的快 – 是目前最快的纯python全文搜索引擎。
良好的构架,评分模块/分词模块/存储模块等各个模块都是可插拔的。
功能强大的查询语言(通过pyparsing实现功能)。
纯python实现的拼写检查(目前唯一的纯python拼写检查实现)
Jieba: 如果把用户输入的内容全部拿去检索那不就和数据库的模糊查询一个意思了嘛,所以我需要一个能分词的工具。Jieba是一款免费的中文分词包,由于Whoosh自带的是英文分词,对中文的分词支持不是太好,故用jieba替换whoosh的分词组件。
haystack: 现在检索引擎和分词库都有了,那么接下来就是如何将这两个整合到我们的项目中了。haystack是一款同时支持whoosh,solr,Xapian,Elasticsearc四种全文检索引擎的第三方app,这意味着如果你不想使用whoosh,那你随时可以将之更换成Xapian等其他搜索引擎,而不用更改代码。正如Django一样,Django-haystack的使用也非常简单。
1 pip install django-haystack whoosh jieba
2、项目工程目录截图
1 其中inspiration_book为项目app,account为用户app(主要是登录注册等常见用户行为),note为笔记app即对笔记的所有操作
3、创建note应用,编辑settings.py配置文件,添加note
和haystack
1 2 3 4 5 6 7 8 vim settings.py ############### INSTALLED_APPS = ( ... 'note', 'haystack', ) ###############
4、编辑note/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # -*- coding: utf-8 -*- class NoteBook(models.Model): name = models.CharField(max_length=16,unique=True) user = models.CharField(max_length=20,default='1') def __unicode__(self): return ' %s' % (self.name) class Note(models.Model): title = models.CharField(max_length=32,unique=True) user = models.CharField(max_length=20,default='1') content = models.CharField(max_length=16) tag = models.CharField(max_length=10) notebook = models.CharField(max_length=20,default='notebook1') def __unicode__(self): return '%d: %s' % (self.pk, self.title)
其中tag字段代表笔记的标签,notebook代表笔记所在的笔记本(在这里之所以没有使用外键,是因为NoteBook的name列不重复,即通过这样设计可以更容易代码逻辑同时满足笔记本移动、笔记合并、垃圾箱等操作)
5、在note应用目录下,添加一个索引 编辑note/search_indexes.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # -*- coding: utf-8 -*- from haystack import indexes # 修改此处,为你自己的model from note.models import Note # 修改此处,类名为模型类的名称+Index,比如模型类为Note,则这里类名为NoteIndex class NoteIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) def get_model(self): return Note def index_queryset(self, using=None): """Used when the entire index for model is updated.""" return self.get_model().objects.all()
如果你想针对某个app例如note做全文检索,则必须在note的目录下面建立search_indexes.py文件,其中文件名是不能修改的。
这里为Note创建一个索引是为了为指定的数据添加一个索引(目录),通过这样的方法可以避免在查询过程中查询了所有满足搜索条件的数据,能够减少服务器的负担。
接下来我们了解一下它的哪些字段创建索引,怎么指定。
每个索引里面必须有且只能有一个字段为 document=True,这代表haystack 和搜索引擎将使用此字段的内容作为索引进行检索(primary field)。其他的字段只是附属的属性,方便调用,并不作为检索数据。
注意:如果使用一个字段设置了document=True,则一般约定此字段名为text,这是在SearchIndex类里面一贯的命名,以防止后台混乱,当然名字你也可以随便改,不过不建议改。
并且,haystack提供了use_template=True在text字段,这样就允许我们使用数据模板去建立搜索引擎索引的文件,说得通俗点就是索引里面需要存放一些什么东西,例如 Note 的 title 字段,这样我们可以通过 title 内容来检索 Note 数据了,举个例子,假如你搜索 django
,那么就可以检索出title中含有django
的Note了。
数据模板的路径为,文件名必须为要索引的类名_text.txt
:
1 templates/search/indexes/<yourapp>/<ModelName>_text.txt
在这里我们可以通过笔记的标题、内容、笔记本名称和标签来进行搜索。
6、创建并编辑note/my_searchview.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 # encoding: utf-8 from __future__ import absolute_import, division, print_function, unicode_literals import json from django.conf import settings from django.core.paginator import InvalidPage, Paginator from django.http import Http404, HttpResponse from haystack.forms import ModelSearchForm from haystack.query import EmptySearchQuerySet RESULTS_PER_PAGE = getattr(settings, 'HAYSTACK_SEARCH_RESULTS_PER_PAGE', 20) # from haystack.forms import FacetedSearchForm, # from django.shortcuts import render def basic_search(request, load_all=True, form_class=ModelSearchForm, searchqueryset=None, extra_context=None, results_per_page=None): """ A more traditional view that also demonstrate an alternative way to use Haystack. Useful as an example of for basing heavily custom views off of. Also has the benefit of thread-safety, which the ``SearchView`` class may not be. Template:: ``search/search.html`` Context:: * form An instance of the ``form_class``. (default: ``ModelSearchForm``) * page The current page of search results. * paginator A paginator instance for the results. * query The query received by the form. """ query = '' results = EmptySearchQuerySet() if request.GET.get('q'): form = form_class(request.GET, searchqueryset=searchqueryset, load_all=load_all) if form.is_valid(): query = form.cleaned_data['q'] results = form.search() else: form = form_class(searchqueryset=searchqueryset, load_all=load_all) paginator = Paginator(results, results_per_page or RESULTS_PER_PAGE) try: page = paginator.page(int(request.GET.get('page', 1))) except InvalidPage: result = {"code": 404, "msg": 'No file found!', "data": []} return HttpResponse(json.dumps(result), content_type="application/json") context = { 'form': form, 'page': page, 'paginator': paginator, 'query': query, 'suggestion': None, } if results.query.backend.include_spelling: context['suggestion'] = form.get_suggestion() if extra_context: context.update(extra_context) jsondata = [] print(len(page.object_list)) for result in page.object_list: data = { 'pk': result.object.pk, 'title': result.object.title, 'content': result.object.content, 'notebook': result.object.notebook, 'user_id': result.object.user, 'tag': result.object.tag } jsondata.append(data) result = {"code": 200, "msg": 'Search successfully!', "data": jsondata} return HttpResponse(json.dumps(result), content_type="application/json")
这里由于自身在项目中只承担后台开发的任务,于是需要返回json形式的数据给中间层或前端。 经过尝试之后,可以发现page.object_list
的类型为SearchResult
,且可以通过迭代器获取我们需要的笔记内容,于是通过将其转换为json并返回HttpResponse
。 这里name="q"
是搜索框架中view使用到的,详情查看源码或官方文档
其中一些变量和对应的释义如下:
变量
含义
query
搜索的字符串
page
当前页的page对象
paginator
分页paginator对象
7、编辑项目的urls.py
1 2 3 4 5 6 7 8 from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url('account/', include('account.urls')), url('file/', include('note.urls')), ]
其实haystack.urls的内容为:
1 2 3 4 5 6 7 8 from django.conf.urls import url from note import views, my_searchview urlpatterns = [ ... url('^search_keywords/$', my_searchview.search_keywords), ... ]
在这里
8、指定索引模板文件 在templates/search/indexes/应用名称/
下创建模型名称_text.txt
文件。 此文件指定将模型中的哪些字段建立索引,写入如下内容:(只修改中文,不要改掉object)
1 2 3 {{ object.字段1 }} {{ object.字段2 }} {{ object.字段3 }}
这里我们创建inspiration_notebook/note/templates/search/indexes/note/note_text.txt
1 2 3 4 {{ object.title }} {{ object.content}} {{ object.notebook }} {{ object.tag }}
这个数据模板的作用是对Note.title,Note.content,Note.notebook,Note.tag这四个字段建立索引,当检索的时候会对这四个字段做全文检索匹配。
9、使用jieba中文分词 拷贝whoosh_backend.py
到blog应用目录下,并重命名whoosh_cn_backend.py
1 cp /usr/lib/python2.7/site-packages/haystack/backends/whoosh_backend.py /blog/whoosh_cn_backend.py
然后修改whoosh_cn_backend.py
1 2 3 4 #在顶部添加 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
1 2 3 4 5 6 7 8 9 10 11 12 13 # 全文检索框架配置 HAYSTACK_CONNECTIONS = { 'default': { # 修改后的whoosh引擎,支持中文分词 'ENGINE': 'note.whoosh_cn_backend.WhooshEngine', # 索引文件路径 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), } } # 当添加、修改、删除数据时,自动生成索引 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' # 指定搜索结果每页显示多少条信息 HAYSTACK_SEARCH_RESULTS_PER_PAGE = 6
其他引擎配置参考官方文档
10、同步数据库
1 2 python manage.py makemigrations python migrate
11、数据库blog_note表插入数据(略) 在这里如果因为修改数据库的设计,由于一些键值得影响,可以通过先将数据库文件保存到本地(因为有真实有用的数据),再通过sql完成操作后upload即可(我这里因为服务器安装的工具较少,所以采取了偷懒的方式)
12、生成索引 手动生成一次索引:
1 python manage.py rebuild_index
13、通过正常在服务器运行django的方式运行即可了。
1 2 3 例如: python manage.py runserver 0.0.0.0:8000 可以通过使用nohup命令保持程序运行并输出log到本地。
测试一下效果: 输入搜索 (图片待补)
返回结果 (图片待补)
14、参考博客  [1] https://www.cnblogs.com/chichung/p/10017539.html  [2] https://blog.csdn.net/qq_42664045/article/details/88305489  [3] https://github.com/fish2018/searcher
其他中文分词工具:https://www.cnblogs.com/qqhfeng/p/5321949.html