Django全文检索实现:HayStack + Whoosh + Jieba

涉及到的工具

  • haystack是django的开源搜索框架,该框架支持**Solr, **ElasticsearchWhoosh, ***Xapian*搜索引擎,不用更改代码,直接切换引擎,减少代码量。

  • 搜索引擎使用Whoosh,这是一个由纯Python实现的全文搜索引擎,没有二进制文件等,比较小巧,配置比较简单,当然性能自然略低。

  • 中文分词Jieba,由于**Whoosh**自带的是英文分词,对中文的分词支持不是太好,故用**jieba**替换**whoosh**的分词组件。

Model配置

class Post(models.Model):
    """post 文章页面"""
    id = models.AutoField(primary_key=True, max_length=20)
    name = models.CharField(max_length=200, help_text="Post文章的URL链接名称")
    post_category = models.ForeignKey(PostCategory, blank=True, null=True, default=None, on_delete=models.SET_NULL)
    post_author = models.ForeignKey(PostAuthor, blank=True, null=True, default=None, on_delete=models.SET_NULL)
    post_date = models.DateTimeField(default=timezone.now, help_text="创建日期")
    post_modified = models.DateTimeField(default=timezone.now, help_text="修改日期")
    post_content = models.TextField(help_text="html格式的页面内容,仅在page类型才可用")
    post_title = models.CharField(max_length=200)
    post_summary = models.TextField(max_length=300, blank=True)
    post_status = models.CharField(choices=(('publish', '已发布'), ('draft', '草稿')), default='publish', max_length=20,
                                   help_text="页面状态")

    # post_slug = models.CharField(max_length=200, help_text="URL链接名称")

    def __str__(self):
        return self.post_title

安装Python模块

  • whoosh
  • django-haystack
  • jieba

修改 whoosh 的分析器

将文件whoosh_backend.py(该文件路径为python路径/lib/python版本/site-packages/haystack/backends/whoosh_backend.py)拷贝到app文件夹下面,修改如下 添加from jieba.analyse import ChineseAnalyzer

修改为如下

schema_fields[field_class.index_fieldname] =
    TEXT(stored=True, analyzer=ChineseAnalyzer(),
            field_boost=field_class.boost)

修改settings.py

  • 添加 Haystack 到Django的 INSTALLED_APPS
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'Core.whoosh_backend.WhooshEngine',
        'PATH': os.path.join(BASE_DIR, 'Resources', 'index', 'whoosh'),
    },
}

注: - Core 是我的App名称,请根据情况替换成你自己的App文件夹名称 - PATH为存放Whoosh索引文件的文件夹。 - 其他引擎的配置请查阅官方文档。

建立索引

在需要搜索功能的App文件夹下建立search_indexes.py文件,用于创建索引。

内容如下:

import datetime
from haystack import indexes
from Core import models


class PostIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)

    post_author = indexes.CharField(model_attr='post_author')
    post_date = indexes.DateTimeField(model_attr='post_date')

    def get_model(self):
        return models.Post

    def index_queryset(self, using=None):
        """Used when the entire index for model is updated."""
        return self.get_model().objects.filter(post_date__lte=datetime.datetime.now())

每个索引里面必须有且只能有一个字段为document=True,这代表haystack 和搜索引擎将使用此字段的内容作为索引进行检索(primary field)。其他的字段只是附属的属性,方便调用,并不作为检索数据。

注意:如果使用一个字段设置了document=True,则一般约定此字段名为text,这是在SearchIndex类里面一贯的命名,以防止后台混乱,当然名字你也可以随便改,不过不建议改。

并且,haystack提供了use_template=True在text字段,这样就允许我们使用数据模板去建立搜索引擎索引的文件,使用方便(官方推荐,当然还有其他复杂的建立索引文件的方式,目前我还不知道),数据模板的路径为yourapp/templates/search/indexes/yourapp/classname_text.txt,例如本例子为Resources/templates/search/indexes/Core/post_text.txt 文件名必须为要索引的类名_text.txt,其内容为:

{{ object.post_title }}
{{ object.post_author }}
{{ object.post_content }}
{{ object.post_summary }}

这个数据模板的作用是对 - Post.post_title - Post.post_author - Post.post_content - Post.post_summary

这四个字段建立索引,当检索的时候会对这三个字段做全文检索匹配。

配置URL

在链接配置中加入

    path('search/', include('haystack.urls')),

编写search.html

templates/search/ 文件夹下添加 search.html 文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>Search</h2>

<form method="get" action=".">
    <table>
        {{ form.as_table }}
        <tr>
            <td> </td>
            <td>
                <input type="submit" value="Search">
            </td>
        </tr>
    </table>

    {% if query %}
        <h3>Results</h3>

        {% for result in page.object_list %}
            <p>
                <a href="{{ result.object.get_absolute_url }}">{{ result.object.post_title }}</a>
            </p>
        {% empty %}
            <p>No results found.</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 %}«
                Previous{% if page.has_previous %}</a>{% endif %}
                |
                {% if page.has_next %}<a href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}Next »
                {% if page.has_next %}</a>{% endif %}
            </div>
        {% endif %}
    {% else %}
        {# Show some example queries to run, maybe query syntax, something else? #}
    {% endif %}
</form>
</body>
</html>

建立索引

使用python manage.py rebuild_index或者使用update_index命令。

在 settings.py 里加入以下配置,实现自动刷新索引。

HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor"

这样就OK啦,可以打开 网站/search 试试看搜索功能了~

SearchQuerySet 初窥

我发现网络上的资料都没有介绍到,于是在官网翻看了一下。 文档地址:https://django-haystack.readthedocs.io/en/v2.8.1/searchqueryset_api.html#ref-searchqueryset-api

这个 SearchQuerySet 类似于DjangoORM框架里的 QuerySet,熟悉Django的同学应该很快能上手,不过做结果处理的话会比较麻烦。 用到SearchQuerySet一般都是用于Ajax API,我发现之前用于 QuerySet 的序列化器不能用于 SearchQuerySet,要做的话还需要做一些修改。 这个等有时间再继续改。或者我再看看官网文档,研究一下。

用法挺简单的,看一下官方文档就行,已经写得很清楚了~

About


了解更多有趣的操作请关注我的微信公众号:DealiAxy 每一篇文章都在我的博客有收录:blog.deali.cn