jjzjj

Django+haystack+jieba进行全文检索

Windyzhao0 2023-11-27 原文

最近,在做一个全文检索的功能,找了两个方案:

  1. mysql的全文检索索引
    1. 优点:配置起来简单,改mysql配置即可
    2. 缺点:无法在django使用模型生成,查询语句也无法使用orm,只能用原生sql
  2. 基于Django+haystack+jieba的全文检索
    1. 优点:有第三方库django-haystack直接和django进行关联,还有drf-haystack第三方库支持drf的写法
    2. 缺点:配置比较麻烦,需要自己生成索引,维护索引

综上所述,考虑项目的实际情况,最后考虑使用第二种方法,基于Django+haystack+jieba进行全文检索。

1.相关概念

​ 此方法是在django框架下,使用haystack和中文分词jieba生成索引,索引引擎使用的是默认的引擎whoosh。若有条件或者是数据量大,推荐使用elasticsearch。es的效果比whoosh的效果好很多,且可以做更多的功能和操作。

2.前期准备

​ 先下载依赖包

pip install whoosh
pip install jieba
pip install django-haystack
pip install drf_haystack

3.项目配置

​ 1.首先在django项目中,创建一个app.(repository为自己的app名称)

python manage.py startapp repository

​ 2.在settings的INSTALLED_APPS里注册此app和haystack

INSTALLED_APPS = [
    'haystack', # 放在头部
		 ...
    "rest_framework",
    "repository",
]

​ 3.在settings中配置搜索引擎

HAYSTACK_CONNECTIONS = {
    'default': {
        # 使用whoosh引擎
        'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
        # 索引文件路径
        'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
    }
}

# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

​ 4.创建模型

from django.db import models


class Repository(models.Models):
    """
    文章
    """
    title = models.CharField(max_length=255, help_text="文章标题")
    content = models.TextField(help_text="文章内容")

    class Meta:
        verbose_name = '文章'

​ 5.在repository的app下创建文件search_indexes.py(此名称固定)

from haystack import indexes  # 导入索引
from .models import Repository  # 导入模型


# RepositoryIndex是固定格式命名,Repository是你models.py中的类名

class RepositoryIndex(indexes.SearchIndex, indexes.Indexable):
  	# document=True: 字段名,所有需要索引的字段,一般命名为 text 一个索引类只会有一个
    text = indexes.CharField(document=True, use_template=True) 
    title = indexes.CharField(model_attr="title") # model_attr指定为对应模型的哪个字段
    content = indexes.CharField(model_attr="content")


    def get_model(self):
        return Repository # 返回需要生成索引的模型

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

​ 6.在templates文件夹创建索引文件,文件的层级为templates/search/indexes/repository,在repository文件夹下创建文件repository_text.txt (文件夹层级为必须固定格式,repository_text.txt格式为app名称_text.txt)。在repository_text.txt中编写如下:

{{ object.title }} # 对应Repository的title
{{ object.content }}# 对应Repository的content

意思为使用Repository模型的title和content作为索引进行查询,也可以创建多个字段。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNbuiqiS-1649311534405)

​ 7.设置分词工具为jieba. 在repository包下,创建文件whoosh_cn_backend.py,文件内容为所在虚拟环境下jieba依赖包的

/***/anaconda3/envs/py363/lib/python3.6/site-packages/haystack/backends/whoosh_backend.py(本人环境)。也就是把下载好的haystack的依赖文件whoosh_backend.py复制一份出来到whoosh_cn_backend.py里。然后修改whoosh_cn_backend.py里的代码,在导入包的头部加入from jieba.analyse import ChineseAnalyzer。然后修改旧的StemmingAnalyzerChineseAnalyzer


# 在全局引入的最后一行加入jieba分词器
from jieba.analyse import ChineseAnalyzer # 导包的头部导入 

''''''
''''''

# 找到StemmingAnalyzer 把它改为ChineseAnalyzer
schema_fields[field_class.index_fieldname] = TEXT(
  stored=True,
  # analyzer=field_class.analyzer or StemmingAnalyzer(),
  analyzer=field_class.analyzer or ChineseAnalyzer(),
  field_boost=field_class.boost,
  sortable=True,
)

''''''
''''''


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BuiQyRoQ-1649311534406)

最后在settings里,把配置改为引用whoosh_cn_backend

HAYSTACK_CONNECTIONS = {
    'default': {
        # 使用whoosh引擎
        # 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
        #
        'ENGINE': 'repository.whoosh_cn_backend.WhooshEngine', # repository下的whoosh_cn_backend文件里的WhooshEngine
        # 索引文件路径
        'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
    }
}

​ 8.生成索引,会在项目中生成索引文件目录.

python manage.py rebuild_index

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eyg4UTDM-1649311534406)

编写接口

1.使用drf-haystack编写接口

​ 首先编写视图,查询时,使用text进行查询.(http://127.0.0.1:8000/repository/?text=文章&page=1&page_size=20)

import jieba

from django.db import transaction
from haystack.query import SearchQuerySet
from rest_framework.response import Response
from packages.drf.viewsets import ModelViewSet

from repository.models import Repository
from repository.serializers import RepositorySerializer, RepositoryIndexSerializer
from repository.pages import LargePageNumberPagination


class RepositoryModelViewSet(ModelViewSet):
    queryset = Repository.objects.all()
    serializer_class = RepositorySerializer
    pagination_class = LargePageNumberPagination

    # 如果未传递查询参数 返回的数据将为空 故须根据查询条件调整查询的地方
    # 不查询 查询模型 查询 查询索引数据
    def list(self, request, *args, **kwargs):
        search = request.GET.get("text") # 查询时,使用text进行查询
        if search:
            index_queryset = SearchQuerySet() # 核心 SearchQuerySet为索引查询集
            search_list = jieba.cut_for_search(search)
            for search_name in search_list:
                if not search_name:
                    continue
                index_queryset = index_queryset.filter_and(text=search_name)
						
            # 使用分页器 
            paginator = self.pagination_class()
            page_user_list = paginator.paginate_queryset(index_queryset, self.request, view=self)
            instances = RepositoryIndexSerializer(page_user_list, many=True)
            response = paginator.get_paginated_response(instances.data)
            return response

        return super().list(request, *args, **kwargs)

    @transaction.atomic
    def create(self, request, *args, **kwargs):
        data = request.data
        instance_data = {
            'title': data['title'],
            'content': data["content"],
        }
        instance = Repository.objects.create(**instance_data)
        return Response(instance.id)

路由:
router = SimpleRouter()
router.register(r"repository", RepositoryModelViewSet, basename="repository")
urlpatterns += router.urls
序列化器:
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer
from drf_haystack.serializers import HaystackSerializer
from drf_haystack import serializers as drf_haystack_serializers

from repository.models import Repository
from repository.search_indexes import RepositoryIndex


class RepositoryIndexSerializer(HaystackSerializer):
    """
    全文检索的序列化器
    """
    title = drf_haystack_serializers.HaystackCharField()
    content = drf_haystack_serializers.HaystackCharField()

    class Meta:
        index_classes = [RepositoryIndex]
        search_fields = ["text"]
        fields = ["id", "title", "content"]


class RepositorySerializer(ModelSerializer):

    class Meta:
        model = Repository
        fields = ["id", "title", "content"]


对于SearchQuerySet(),是django-haystack的索引查询集。是查询核心。所有的查询数据从这个查询集合来。它的models方法可以指定索引查询的模型。

SearchQuerySet().filter(content='foo').models(BlogEntry, Comment)

使用SearchQuerySet()查询,可以使用filter或者filter_or或者filter_and。具体的接口参考API文档。

使用SearchQuerySet()进行查询操作,查询到的数据和drf一样正常使用分页器。

使用drf-haystack

​ 使用drf-haystack,用法和django的drf大同小异。需要注意的是,drf-haystack继承了django的drf,故我们可以使用drf的写法操作它。

from drf_haystack import serializers as drf_haystack_serializers

class RepositoryIndexSerializer(HaystackSerializer):
    """
    全文检索的序列化器
    """
    title = drf_haystack_serializers.HaystackCharField()
    content = drf_haystack_serializers.HaystackCharField()
    other_content = drf_haystack_serializers.serializers.SerializerMethodField()


    class Meta:
        index_classes = [RepositoryIndex] # 索引模型
        search_fields = ["text"] # 查询字段
        fields = ["id", "title", "content"] # 返回字段

    def get_other_content(self, obj):
        return obj.object.content

至此,从配置,到生成索引。从编写模型到序列化器,从路由到视图,都可以使用到全文检索。我们可以很快速的编写出全文检索的接口。另外,还有一些参考的文章和相关的API文档,我放在末尾进行参考。

注意:若转发,请附上原文链接!

参考文档

haystack官网文档

drf-haystack官方文档

有关Django+haystack+jieba进行全文检索的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  5. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  6. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  7. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  8. ruby-on-rails - 使用 HTTP.get_response 检索 Facebook 访问 token 时出现 Rails EOF 错误 - 2

    我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token

  9. ruby - 捕获 Ruby Logger 输出以进行测试 - 2

    我有一个像这样的ruby​​类:require'logger'classTdefdo_somethinglog=Logger.new(STDERR)log.info("Hereisaninfomessage")endend测试脚本行如下:#!/usr/bin/envrubygem"minitest"require'minitest/autorun'require_relative't'classTestMailProcessorClasses当我运行这个测试时,out和err都是空字符串。我看到消息打印在stderr上(在终端上)。有没有办法让Logger和capture_io一起玩得

  10. ruby - 按数字(从大到大)然后按字母(字母顺序)对对象集合进行排序 - 2

    我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby​​做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排

随机推荐