django 基于haystack 基于elasticsearch实现全文搜索

目录

1、elasticsearch配置

1、安装elasticsearch

2、IK中文分词器

3、elasticsearch-head

4、kibana

5、快速入门

倒排索引

2、django基于haystack使用elasticsearch全文搜索

2、1安装haystack

2、基本使用

2、1 安装配置

2、2 索引模型

2、3 全文索引字段模板

2、4 全文搜索的索引视图

2、5 路由

2、6 手动构建es索引


1、elasticsearch配置

1、安装elasticsearch

支持单点部署和集群部署

# 从课件的素材中找到es的镜像压缩包,复制到ubuntu桌面下,执行以下命令
# sudo docker load -i ~/Desktop/elasticsearch.7.13.4.tar.gz
podman load -i ~/Desktop/elasticsearch.7.13.4.tar.gz
# 也可以不执行上面的操作,直接run可以让docker从官网拉取es镜像。

# sudo docker run --name elasticsearch --restart=always -d -p 9200:9200 -p 9300:9300 -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -e "discovery.type=single-node" elasticsearch:7.13.4

podman run --name elasticsearch -d -p 9200:9200 -p 9300:9300 -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -e "discovery.type=single-node" elasticsearch:7.13.4

  参数说明:

--name elasticsearch  
       设置当前容器的容器名称为elasticsearch
--restart=always
       设置容器开机自启,即便物理机关机重启了,docker在启动以后也会自动帮我们把当前容器启动起来。
-d     设置当前容器为守护式容器,在后台运行
-p 9200:9200
       设置端口影射,<物理机端口>:<容器端口>
       表示访问了当前物理机的9200,相当于访问了当前容器的9200端口
-p 9300:9300
       设置端口影射,<物理机端口>:<容器端口>
       表示访问了当前物理机的9300,相当于访问了当前容器的9300端口
-e ES_JAVA_OPTS="-Xms256m -Xmx256m"
       设置环境变量,变量名为ES_JAVA_OPTS,这个变量是启动elasticsearch的关键。
       表示设置java环境的最小和最大使用内存,内存不足,elasticsearch是无法启动的,所以此处设置为最小内存必须在256M以上
-e "discovery.type=single-node"
       设置环境变量,变量名discovery.type
       discovery.type 表示当前elasticsearch的运作模式为single-node,表示单机部署/单点部署
elasticsearch:7.13.4
       设置当前容器的镜像名和版本号

浏览器访问:http://127.0.0.1:9200

要基于es实现全文搜索,可以参考以下文档了解关于全文搜索的内容。

全文搜索 | Elasticsearch: 权威指南 | Elastic

全文搜索的实现,必须依靠es内部调用分词器对语句进行词性分析,拆词,给每一个单词构建一个索引。

所以默认情况下,es只提供了标准分析器,和简单分析器,这几块分词器都是只能针对英文进行分词。

2、IK中文分词器

默认情况下,elasticsearch是外国开发的,所以本身对于中文分词构建分词索引的支持是不行的。所以我们需要在elasticsearch软件中新增一个支持中文索引和中文分词的插件,叫ik分词器。

注意:IK分词器插件的版本必须与elasticsearch的版本号同步。否则安装失败!

文档:https://github.com/medcl/elasticsearch-analysis-ik/releases

把IK分词器解压并复制到elasticsearch容器的/usr/share/elasticsearch/plugins目录下

unzip ~/Desktop/elasticsearch-analysis-ik-7.13.4.zip -d ~/Desktop/ik-7.13.4
podman cp ~/Desktop/ik-7.13.4 elasticsearch:/usr/share/elasticsearch/plugins
podman stop elasticsearch
podman start elasticsearch

注意:elasticsearch内部极其复杂,所以启动容器以后需要等待1分钟左右才对外提供搜索服务。

接下来,我们就可以通过postman测试。

注意:es提供的9200是restful api接口的端口,以http形式访问,9300端口是Api对服务器的管理端口。

post http://127.0.0.1:9200/_analyze?pretty

基于智能分词模式来查询分析词性,json数据

{
   "analyzer":"ik_smart",
   "text":"我是中国人"    
}

基于最大分词模式来查询分析词性,json数据

{
   "analyzer":"ik_max_word",
   "text":"我是中国人"  
}

3、elasticsearch-head

elasticsearch-head 是用于监控 Elasticsearch 状态的客户端插件,包括数据可视化、执行增删改查操作等。不过开发中,我们一般使用elasticsearch-head来查看elasticsearch的数据而已,真正对elasticsearch进行增删查改操作一般我们使用kibana或者postman或者编程语言实现的客户端来完成。

我们可以通过docker安装elasticsearch-head来对Elasticsearch 进行界面化管理。

# 拉取镜像
# sudo docker pull mobz/elasticsearch-head:5
podman pull mobz/elasticsearch-head:5

# 创建容器
# sudo docker create --name elasticsearch-head -p 9100:9100 mobz/elasticsearch-head:5
podman create --name elasticsearch-head -p 9100:9100 mobz/elasticsearch-head:5

# 启动容器
# sudo docker start elasticsearch-head
podman start elasticsearch-head

访问elasticsearch-head:http://127.0.0.1:9100/,会发现无法连接elasticsearch,原因是因为跨域问题导致。

解决方案:就是修改容器elasticsearch中的elasticsearch.yml文件增加跨域支持即可

# sudo docker cp elasticsearch:/usr/share/elasticsearch/config/elasticsearch.yml ~/Desktop/elasticsearch.yml
podman cp elasticsearch:/usr/share/elasticsearch/config/elasticsearch.yml ~/Desktop/elasticsearch.yml

修改elasticsearch.yml内容,增加跨域支持,如下:

cluster.name: "docker-cluster"
network.host: 0.0.0.0
http.cors.enabled: true 
http.cors.allow-origin: "*"

把elasticsearch.yml文件再次复制到容器elasticsearch中,并重启容器elasticsearch。

# sudo docker cp ~/Desktop/elasticsearch.yml elasticsearch:/usr/share/elasticsearch/config/elasticsearch.yml
podman cp ~/Desktop/elasticsearch.yml elasticsearch:/usr/share/elasticsearch/config/elasticsearch.yml

修改容器elasticsearch-head的vendor.js让elasticsearch-head界面可以操作elasticsearch

# sudo docker cp elasticsearch-head:/usr/src/app/_site/vendor.js ~/Desktop/vendor.js
podman cp elasticsearch-head:/usr/src/app/_site/vendor.js ~/Desktop/vendor.js

修改vendor.js内容,把6886行与7573行所在的"application/x-www-form-urlencoded"替换成"application/json;charset=UTF-8",并保存文件,复制回容器elasticsearch-head中。

# sudo docker cp ~/Desktop/vendor.js  elasticsearch-head:/usr/src/app/_site/vendor.js
podman cp ~/Desktop/vendor.js elasticsearch-head:/usr/src/app/_site/vendor.js

重启elasticsearch和elasticsearch-head容器

# sudo docker stop elasticsearch
# sudo docker start elasticsearch
podman stop elasticsearch
podman start elasticsearch

# sudo docker stop elasticsearch-head
# sudo docker start elasticsearch-head
podman stop elasticsearch-head
podman start elasticsearch-head

4、kibana

Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。

kibana的版本必须与Elasticsearch一致,所以我们安装的kibana也是7.13.4版本。

# sudo docker pull kibana:7.13.4
# sudo docker run -d --name kibana -e ELASTICSEARCH_URL=http://127.0.0.1:9200 -p 5601:5601 --restart=always kibana:7.13.4

podman pull kibana:7.13.4
podman run -d --name kibana -p 5601:5601 kibana:7.13.4

修改让kibana能访问到Elasticsearch并完成汉化操作

# sudo docker exec -it kibana bash
podman  exec -it kibana bash
vi config/kibana.yml

kibana.yml中的内容需改如下:

#
# ** THIS IS AN AUTO-GENERATED FILE **
#

# Default Kibana configuration for docker target
server.host: "0.0.0.0"
# 注意:此处的IP地址替换为网卡地址,不能使用127.0.0.1或localhost,否则无法访问,可以通过ip a来查看
elasticsearch.hosts: [ "http://IP地址:9200" ]
monitoring.ui.container.elasticsearch.enabled: true
i18n.locale: "zh-CN"

修改完成以后,退出当前kibana容器,并重启kibana容器即可。

# sudo docker stop kibana
# sudo docker start kibana
podman stop kibana
podman start kibana

等待1分钟左右,打开浏览器直接访问http://127.0.0.1:6501,即可。

5、快速入门

倒排索引

倒排索引(Inverted Index),是Elasticsearch中的索引工作机制。倒排索引是区别于正排索引的概念:

  • 正排索引:是以文档对象的唯一ID作为索引,以文档内容作为记录。

  • 倒排索引:指的是将文档内容中的单词作为索引,将包含该词的文档ID作为记录。

Elasticsearch的工作流程如下,因为使用倒排索引产生的文档记录要比mysql数据行少会快

2、django基于haystack使用elasticsearch全文搜索

2、1安装haystack

django-> mysql --> ORM

dango-> haystack --> ORM

haystack是django的开源搜索框架,能够结合目前市面上大部分的搜索引擎用于实现自定义搜索功能,特别是全文搜索。

haystack支持多种搜索引擎,不仅仅是 jieba ,whoosh,使用solr、elasticsearch等搜索,也可通过haystack,而且直接切换引擎即可,甚至无需修改搜索代码。中文分词最好的就是jieba和elasticsearch+ik。

github: https://github.com/rhblind/drf-haystack

# python操作elasticsearch的模块,注意对应版本,类似pymysql
pip install -U elasticsearch==7.13.4
# django开发的haystack的模块,务必先安装drf`-haystack,接着才安装django-haystack。因为drf-haystack不支持es7
pip install -U drf-haystack
pip install -U django-haystack

注意:不要安装django-haystack v3.11版本

2、基本使用

2、1 安装配置

文档:Basic Usage — drf_haystack 1.8.6 documentation

INSTALLED_APPS = [
	# 必须在自己创建的子应用前面
	'haystack',

	# 自己创建的子应用
]

# haystack连接elasticsearch的配置信息
HAYSTACK_CONNECTIONS = {
    'default': {
        # haystack操作es的核心模块
        'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine',
        # es服务端地址
        'URL': 'http://127.0.0.1:9200/',
        # es索引仓库
        'INDEX_NAME': 'haystack',
    },
}

# 当mysqlORM操作数据库改变时,自动更新es的索引,否则es的索引会找不到新增的数据
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

2、2 索引模型

在courses子应用下创建search_indexes.py,用于设置es的索引模型。注意,索引模型的文件名必须是search_indexes。类名一定是模型名+Index(CourseIndex)

from haystack import indexes
from .models import Course

class CourseIndex(indexes.SearchIndex, indexes.Indexable):
    # 全文索引[可以根据配置,可以包括多个字段索引]
    # document=True 表示当前字段为全文索引
    # use_template=True 表示接下来haystack需要加载一个固定路径的html模板文件,让text与其他索引字段绑定映射关系
    text = indexes.CharField(document=True, use_template=True)
    # 普通索引[单字段,只能提供单个字段值的搜索,所以此处的声明更主要是为了提供给上面的text全文索引使用的]
    # es索引名 = indexes.索引数据类型(model_attr="ORM中的字段名")
    id = indexes.IntegerField(model_attr="id")
    name = indexes.CharField(model_attr="name")
    description = indexes.CharField(model_attr="description")
    teacher = indexes.CharField(model_attr="teacher__name")
    course_cover = indexes.CharField(model_attr="course_cover")
    get_level_display=indexes.CharField(model_attr="get_level_display")
    students=indexes.IntegerField(model_attr="students")
    get_status_display=indexes.CharField(model_attr="get_status_display")
    lessons=indexes.IntegerField(model_attr="lessons")
    pub_lessons=indexes.IntegerField(model_attr="pub_lessons")
    price=indexes.DecimalField(model_attr="price")
    discount=indexes.CharField(model_attr="discount_json")
    orders=indexes.IntegerField(model_attr="orders")

    # 指定与当前es索引模型对接的mysql的ORM模型
    def get_model(self):
        return Course

    # 当用户搜索es索引时,对应的提供的mysql数据集有哪些?
    def index_queryset(self, using=None):
        return self.get_model().objects.filter(is_deleted=False,is_show=True)

注意:必须时json格式,如果是自定义字段返回的是复杂类型,需要自己转换为json格式,json.dumps()

2、3 全文索引字段模板

全文索引模板必须先配置django项目中的TEMPLATES模板引擎,而且全文索引模板的路径必须是模板目录下的search/indexes/子应用目录名/模型类名_text.txt。否则报错。settings.dev,代码:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / "templates",  # BASE_DIR 是apps的父级目录,是主应用目录,templates需要手动创建
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

创建全文索引字段的html模板,在HTML模板中采用django的模板语法,绑定text与其他es单字段索引的映射关系。

注意:course_text.txt 中course就是ORM模型类名小写,text就是es索引模型类中的全文索引字段名。

templates/search/indexes/courses/course_text.txt。代码:该文件的内容就是索引库中对应的text分词的数据,即全文搜索的关键字来源

{{ object.name }}
{{ object.description }}
{{ object.teacher.name }}
{{ object.category.name }}
{{ object.diretion.name }}

object表示当前orm(course)的模型对应。

注意:每次修改当前文件内容,都需要重新构建一次索引库(坑)

# 重建索引
python manage.py rebuild_index

2、4 全文搜索的索引视图

from drf_haystack.viewsets import HaystackViewSet
from drf_haystack.filters import HaystackFilter
from .serializers import CourseIndexHaystackSerializer
from .models import Course

class CourseSearchViewSet(HaystackViewSet):
    """课程信息全文搜索视图类"""
    # 指定本次搜索的最终真实数据的保存模型
    index_models = [Course]
    serializer_class = CourseIndexHaystackSerializer
    filter_backends = [OrderingFilter, HaystackFilter]
    ordering_fields = ('id', 'students', 'orders')
    pagination_class = CourseListPageNumberPagination

2、5 路由

from django.urls import path,re_path
from . import views

from rest_framework import routers
router = routers.DefaultRouter()
# 注册全文搜索到视图集中生成url路由信息
router.register("search", views.CourseSearchViewSet, basename="course-search")

urlpatterns = [
    path("directions/", views.CourseDirectionListAPIView.as_view()),
    re_path("^categories/(?P<direction>d+)/$", views.CourseCategoryListAPIView.as_view()),
    re_path("^(?P<direction>d+)/(?P<category>d+)/$", views.CourseListAPIView.as_view()),
] + router.urls

2、6 手动构建es索引

因为此前mysql中已经有了部分的数据,而这部分数据在es中是没有创建索引。所以需要先把之前的数据同步生成全文索引。在终端下执行以下命令

# 重建索引
python manage.py rebuild_index

# 更新索引
# python manage.py update_index --age=<num_hours>

# 删除索引
# python manage.py clear_index

测试访问:

http://api.fuguang.cn:8000/courses/search/?text=入门

http://api.fuguang.cn:8000/courses/search/?text=李老师