Django REST framework 全文搜索实战

1,262 阅读3分钟

写在前面

Django REST framework 入门

之前聊到在项目中可以直接使用 Mysql 的FULLTEXT INDEX直接实现全文检索,今天继续这个话题聊一聊Django项目中使用的全文检索 Whoosh

本文的代码是以 Django REST framework (一)为基础开发的

扩展项目

django-haystack 是一个专门提供搜索功能的 django 第三方应用,它还支持 Solr、Elasticsearch、Whoosh、Xapian 等多种搜索引擎,配合著名的中文自然语言处理库 jieba 分词,就可以提供一个效果不错的文字搜索系统。

配置haystack

这里是基于 Django REST framework 的项目继续配置 haystack 所以是 安装 drf-haystack

1. 安装依赖

pipenv install  drf-haystack  whoosh jieba

2. 配置项目

  • 创建文件 article/models.py

from django.db import models


class Article(models.Model):
    creator = models.CharField(max_length=50, null=True, blank=True)
    tag = models.CharField(max_length=50, null=True, blank=True)
    title = models.CharField(max_length=50, null=True, blank=True)
    content = models.TextField()

  • 创建文件 article/search_indexes.py

from .models import Article
from haystack import indexes


class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    title = indexes.CharField(model_attr="title")
    content = indexes.CharField(model_attr="content")
    tag = indexes.CharField(model_attr="tag")
    creator = indexes.CharField(model_attr="creator")
    id = indexes.CharField(model_attr="pk")
    autocomplete = indexes.EdgeNgramField()

    @staticmethod
    def prepare_autocomplete(obj):
        return " ".join((
            obj.title,
        ))

    def get_model(self):
        return Article

    def index_queryset(self, using=None):
        return self.get_model().objects.all()

  • 创建文件 article/serializers.py

from rest_framework import serializers
from drf_haystack.serializers import HaystackSerializer

from .search_indexes import ArticleIndex
from .models import Article


class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'


class ArticleHaystackSerializer(HaystackSerializer):

    def update(self, instance, validated_data):
        pass

    def create(self, validated_data):
        pass

    class Meta:
        index_classes = [ArticleIndex]

        fields = ['title', 'creator', 'content', 'tag']


  • 创建文件 article/urls.py
from django.conf.urls import url, include
from rest_framework import routers
from . import views


router = routers.DefaultRouter()
# router.register('article', views.ArticleViewSet)
router.register("article/search", views.ArticleSearchView, basename='article-search')

urlpatterns = [
    url(r'^', include(router.urls)),

]

  • 创建文件 article/views.py


from .models import Article
from rest_framework import viewsets
from .serializers import ArticleSerializer, ArticleHaystackSerializer
from drf_haystack.viewsets import HaystackViewSet


class ArticleSearchView(HaystackViewSet):

    index_models = [Article]

    serializer_class = ArticleHaystackSerializer


class ArticleViewSet(viewsets.ModelViewSet):
    """
    允许用户查看或编辑的API路径。
    """
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

项目 settings.py 新增

# 指定搜索引擎
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
        'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
    },
}
# 指定如何对搜索结果分页,这里设置为每 10 项结果为一页,默认是 20 项为一页
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10
# 添加此项,当数据库改变时,会自动更新索引,非常方便
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

在INSTALLED_APPS中添加项目

INSTALLED_APPS = [
    ...
    'haystack',
    'article'
]

3. 配置数据

  • 生成数据表
# 生成数据库建模语句
python manage.py makemigrations
 
# 执行建模语句
python manage.py migrate 
  • 初始化数据
INSERT INTO article_article (creator, tag, title, content)
VALUES ('admin', '现代诗', '如果', '今生今世 永不再将你想起
除了
除了在有些个
因落泪而湿润的夜里 如果
如果你愿意'),
	('admin', '现代诗', '爱情', '有一天路标迁了希望你能从容
有一天桥墩断了希望你能渡越
有一天栋梁倒了希望你能坚强
有一天期待蔫了希望你能理解'),
	('admin', '现代诗', '远和近', '你 一会看我
一会看云
我觉得
你看我时很远
你看云时很近'),
	('admin', '现代诗', '断章', '你站在桥上看风景,
看风景人在楼上看你。
明月装饰了你的窗子,
你装饰了别人的梦。'),
	('admin', '现代诗', '独语', '我向你倾吐思念
你如石像
沉默不应
如果沉默是你的悲抑
你知道这悲抑
最伤我心');

4. 构建索引

# 新增文件
templates/search/indexes/article/article_text.txt

{{ object.title }}
{{ object.tag }}
{{ object.content }}
{{ object.creator }}

# 创建索引

$ python manage.py rebuild_index
WARNING: This will irreparably remove EVERYTHING from your search index in connection 'default'.
Your choices after this are to restore from backups or rebuild via the `rebuild_index` command.
Are you sure you wish to continue? [y/N] y
Removing all documents from your index because you said so.
All documents removed.
Indexing 5 articles


5. 查看结果

使用 curl 验证结果,支持多条件查询


$ curl -H 'Accept: application/json; indent=4' -u admin:admin http://127.0.0.1:8000/api/article/search/\?content__contains\=落泪
{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "title": "如果",
            "content": "今生今世 永不再将你想起\n除了\n除了在有些个\n因落泪而湿润的夜里 如果\n如果你愿意",
            "tag": "现代诗",
            "creator": "admin"
        }
    ]
}


$ curl -H 'Accept: application/json; indent=4' -u admin:admin http://127.0.0.1:8000/api/article/search/\?content__contains\=落泪\&title\=如果 
{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "title": "如果",
            "content": "今生今世 永不再将你想起\n除了\n除了在有些个\n因落泪而湿润的夜里 如果\n如果你愿意",
            "tag": "现代诗",
            "creator": "admin"
        }
    ]
}

更换分词工具

由于默认的分词工具对中文的支持不够完善,这里可以更换成为 jieba 分词工具

1. whoosh 配置文件修改

一般文件在当前python 环境中 site-packages 中寻找

# 1. 拷贝 `haystack/backends/whoosh_backends.py` 到当前 ./article

# 2. 搜索 并修改

from jieba.analyse import ChineseAnalyzer # 文件开始新增

把所有的 StemmingAnalyzer 改为 ChineseAnalyser


...
#注意先找到这个再修改,而不是直接添加  
schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=ChineseAnalyzer(),field_boost=field_class.boost, sortable=True)

2. settings 配置文件修改

# 只修改 HAYSTACK 的配置
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'article.whoosh_backend.WhooshEngine',
        'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
    },
}

3. 重新生成索引

$ python manage.py rebuild_index                                                                                                             
WARNING: This will irreparably remove EVERYTHING from your search index in connection 'default'.
Your choices after this are to restore from backups or rebuild via the `rebuild_index` command.
Are you sure you wish to continue? [y/N] y
Removing all documents from your index because you said so.
All documents removed.
Indexing 5 articles
Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/st/b16fyn3s57x_5vszjl599njw0000gn/T/jieba.cache
Loading model cost 0.764 seconds.
Prefix dict has been built successfully.

4. 验证结果

结果一样可以搜索到想要的结果


$ curl -H 'Accept: application/json; indent=4' -u admin:admin http://127.0.0.1:8000/api/article/search/\?content__contains\=风景
{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "title": "断章",
            "content": "你站在桥上看风景,\n看风景人在楼上看你。\n明月装饰了你的窗子,\n你装饰了别人的梦。",
            "tag": "现代诗",
            "creator": "admin"
        }
    ]
}

项目目录

来张项目的全家桶

$ tree  | grep -v pyc
.
├── Pipfile
├── Pipfile.lock
├── article
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── __init__.py
│   ├── models.py
│   ├── search_indexes.py
│   ├── serializers.py
│   ├── urls.py
│   ├── views.py
│   └── whoosh_backend.py
├── demo
│   ├── __init__.py
│   ├── asgi.py
│   ├── serializers.py
│   ├── settings.py
│   ├── urls.py
│   ├── views.py
│   └── wsgi.py
├── manage.py
├── templates
│   └── search
│       └── indexes
│           └── article
│               └── article_text.txt
└── whoosh_index
    ├── MAIN_WRITELOCK
    ├── MAIN_ox1ij98muwsyw2qv.seg
    └── _MAIN_1.toc

总结

到此为止关于全文搜索我们有两种不同的实现方式,对于简单的项目完全可以使用mysql来解决,不会有语言的限制。当然如果你的项目正好是 Django开发的,那么 whoosh + jieba 也是一个不错的选择

参考资料