前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >django设置全文搜索引擎

django设置全文搜索引擎

作者头像
羽翰尘
修改2019-11-26 15:48:45
7020
修改2019-11-26 15:48:45
举报
文章被收录于专栏:技术向技术向

本文由腾讯云+社区自动同步,原文地址 http://blogtest.stackoverflow.club/125/

背景

自己的网站一般都采用直接数据库搜索的方式,一直表现良好(数据量小)。直到某一天我将搜索词从“被掩埋的巨人”变成了“被掩埋 巨人”(中间有空格),数据库返回零。

使用的代码片段如下:

代码语言:txt
复制
search_result = Article.objects.filter(Q(title__icontains=keywords))

很显然,这是由于我采用了`icontains造成的,无法自动分词。遂考虑换为全文搜索。

全文搜索的简单实现

参考官方教程,脚本之家(步骤详细)

按照上面两个教程的设置应该不会出现大问题。

教程中需要强调的地方

虽然上述两个教程已经非常详尽了,但是我在实现的过程中依旧碰到了一些麻烦。可见教程中还是忽略了一些自己并不知晓的东西,强调如下。

  1. 默认路径

简单起见,一般都是先按照教程中的设定做实现,这里就要考虑很多default设定。一般都和model有关。

在全文搜索(中文)教程中,共涉及到以下几个文件。

app路径下(我这里的app文件夹是viewer):

  • search_indexes.py
  • whoosh_cn_backend.py

这两个文件名不需要做变动。

代码语言:txt
复制
├── viewer
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   ├── models.py
│   ├── search_indexes.py
│   ├── tests.py
│   ├── views.py
│   └── whoosh_cn_backend.py

templates路径下

  • search/indexes/viewer/item_text.txt
  • search/search.html

item_text.txt变更为你自己的模型名称,我的模型为item,所以是item_text.txt(未尝试名称不变更的后果)

代码语言:txt
复制
├── templates
│   ├── article.html
│   ├── comments.html
│   ├── pagination.html
│   ├── search
│   │   ├── indexes
│   │   │   └── viewer
│   │   │       └── item_text.txt
│   │   └── search.html
  1. 默认名称

settings.py中有如下代码块:

代码语言:txt
复制
import os
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
        'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
    },
}

其中,ENGINE字段需要根据自己实际情况做变动。如果是英文搜索,直接参考官方教程即可;如果是中文搜索,参考脚本之家的教程,改成whoosh_cn_backend.py所在的路径。

比如,我的whoosh_cn_backend.pyviewer路径下,就可以修改为:

代码语言:txt
复制
HAYSTACK_CONNECTIONS = {
      'default': {
              'ENGINE': 'viewer.whoosh_cn_backend.WhooshEngine',
              'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
            },
}

增加搜索结果高亮

如果我们想要更优雅一些,比如让命中的文字高亮,该如何做呢?参考官方搜索结果高亮教程

总结来看,每次搜索向模板文件返回的结果包含两个要素,pagequery,page中包含分好页的搜索结果,query就是form.cleaned_data['q']语句的返回结果,而form则是ModelSearchForm的实例,它是使用了request.GET的参数来初始化的。

使用highlight标签配合query就可以将搜索结果高亮,主要的工作在template中完成。

一个典型的template文件示例如下:

不要忘了先 load highlight

代码语言:txt
复制
{% load highlight %}
<style>
span.highlighted { color: red; }
</style>
<!--省略无关代码-->
{% highlight result.object.name  with query %}
<!--省略无关代码-->

自定义view

在有些情况下,我们可能要自定义一个view来使用全文搜索的结果。比如说前端页面已经完成,不希望做太大更改;或者请求是post而不是get;或者说要实现聚合搜索,即本地数据库找到结果太少时,像其他主机请求数据。

使用默认的view显然无法满足需求。

还记得吗,在简单实现部分,两个教程都使用了url(r'^search/', include('haystack.urls')),路由,这也是很多文件必须使用默认路径的原因。

由于使用了默认的路由,所有的请求都由haystack处理,实际的处理函数是SearchView(),在库的安装路径可以找到,我的路径是~/.local/lib/python3.5/site-packages/haystack/views.py.

为方便阅读,SearchView的全部代码如下:

代码语言:txt
复制
class SearchView(object):
    template = 'search/search.html'
    extra_context = {}
    query = ''
    results = EmptySearchQuerySet()
    request = None
    form = None
    results_per_page = RESULTS_PER_PAGE
    def __init__(self, template=None, load_all=True, form_class=None, searchqueryset=None, results_per_page=None):
        self.load_all = load_all
        self.form_class = form_class
        self.searchqueryset = searchqueryset
        if form_class is None:
            self.form_class = ModelSearchForm
        if not results_per_page is None:
            self.results_per_page = results_per_page
        if template:
            self.template = template
    def __call__(self, request):
        """
        Generates the actual response to the search.
        Relies on internal, overridable methods to construct the response.
        """
        self.request = request
        self.form = self.build_form()
        self.query = self.get_query()
        self.results = self.get_results()
        return self.create_response()
    def build_form(self, form_kwargs=None):
        """
        Instantiates the form the class should use to process the search query.
        """
        data = None
        kwargs = {
            'load_all': self.load_all,
        }
        if form_kwargs:
            kwargs.update(form_kwargs)
        if len(self.request.GET):
            data = self.request.GET
        if self.searchqueryset is not None:
            kwargs['searchqueryset'] = self.searchqueryset
        return self.form_class(data, **kwargs)
    def get_query(self):
        """
        Returns the query provided by the user.
        Returns an empty string if the query is invalid.
        """
        if self.form.is_valid():
            return self.form.cleaned_data['q']
        return ''
    def get_results(self):
        """
        Fetches the results via the form.
        Returns an empty list if there's no query to search with.
        """
        return self.form.search()
    def build_page(self):
        """
        Paginates the results appropriately.
        In case someone does not want to use Django's built-in pagination, it
        should be a simple matter to override this method to do what they would
        like.
        """
        try:
            page_no = int(self.request.GET.get('page', 1))
        except (TypeError, ValueError):
            raise Http404("Not a valid number for page.")
        if page_no < 1:
            raise Http404("Pages should be 1 or greater.")
        start_offset = (page_no - 1) * self.results_per_page
        self.results[start_offset:start_offset + self.results_per_page]
        paginator = Paginator(self.results, self.results_per_page)
        try:
            page = paginator.page(page_no)
        except InvalidPage:
            raise Http404("No such page!")
        return (paginator, page)
    def extra_context(self):
        """
        Allows the addition of more context variables as needed.
        Must return a dictionary.
        """
        return {}
    def get_context(self):
        (paginator, page) = self.build_page()
        context = {
            'query': self.query,
            'form': self.form,
            'page': page,
            'paginator': paginator,
            'suggestion': None,
        }
        if hasattr(self.results, 'query') and self.results.query.backend.include_spelling:
            context['suggestion'] = self.form.get_suggestion()
        context.update(self.extra_context())
        return context
    def create_response(self):
        """
        Generates the actual HttpResponse to send back to the user.
        """
        context = self.get_context()
        return render(self.request, self.template, context)

可以看出,SearchView类被当做函数调用后,传入的参数是request,之后经过build_form(), get_query(),get_results()后获得搜索结果,返回函数create_response()的运行结果,而在create_response()中又调用了build_page()完成分页。

可以考虑继承SearchView类,接收keywords参数,并构造为一个request.GET对象由父类处理搜索,返回结果无需分页。

如此,我们需要重载build_form(),__call__()两个函数。

代码语言:txt
复制
from haystack.views import SearchView
from django.http import QueryDict
class whoosh_search(SearchView):
    def build_form(self,keywords,form_kwargs=None):
        data = None
        kwargs = {'load_all':self.load_all}
        if form_kwargs:
            kwargs.update(form_kwargs)
        if len(keywords):
            data = QueryDict('q='+keywords)
        if self.searchqueryset is not None:
            kwargs['searchqueryset'] = self.searchqueryset
        return self.form_class(data,**kwargs)
    def __call__(self,keywords):
        self.form = self.build_form(keywords=keywords)
        self.query = self.get_query()
        self.results = self.get_results()
        item_list = []
        for item in self.results:
            item_dict = {}
            item_dict['name'] = item.object.name
            item_dict['author'] = item.object.author
            item_dict['id'] = item.object.id
            item_list.append(item_dict)
        
        return item_list,self.query

注意self.resultsSearchQuerySet对象,迭代之后需要使用.object来取数据对象。

这里使用了QueryDict对象,参考博客. 其实比较tricky,更优雅的方法是跳过form的构造过程,直接使用SearchQuery。 不希望再往深处挖了,只希望这个类能正常工作。

这样,在需要使用搜索引擎时,调用这个类就好了,比如:

代码语言:txt
复制
post_list,query = whoosh_search()('hello')

其他:把类当函数使用

在实现自定义view时,碰到一个语法点觉得很有意思。

SearchView本来是一个类,将它作为url路由的处理函数时需要这样写,url('^search/',SearchView()), 这样在调用的时候就变成了SearchView()(request), 由类中的__call__()函数来具体处理。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 全文搜索的简单实现
  • 教程中需要强调的地方
  • 增加搜索结果高亮
  • 自定义view
  • 其他:把类当函数使用
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档