Django集成Haystack与Elasticsearch实现高效商品搜索

巷口听风
巷口听风
发布时间:2026-10-13 | 2人浏览过
原创

本文详解如何在Django项目中利用Haystack框架对接Elasticsearch,解决海量数据下的商品搜索性能瓶颈。内容涵盖Elasticsearch服务端搭建、IK分词器配置、索引类定义及DRF视图集成,提供从环境部署到API调用的完整实现方案。

引言与背景

前言:

在开发电商类项目时,商品搜索功能是核心需求之一。

面对这一需求,常规思路往往指向数据库的模糊查询,例如执行 select * from tb_sku where name like %苹果手机%。

或者在Django ORM中使用 SKU.objects.filter(name__contains="苹果手机") 进行过滤。

然而,当数据库规模达到千万级且name字段未建立索引时,此类查询可能耗时十余分钟,用户体验极差。若为提升查询速度而建立索引,又会在频繁的数据增删改操作中消耗大量数据库资源,导致性能下降。因此,需要寻找更优的解决方案。

Elasticsearch原理概述

Elasticsearch!

这是一个基于Lucene构建的强大全文搜索引擎,维基百科、Stack Overflow及GitHub等知名平台均在其搜索功能中采用了该技术。

若需深入理解其底层机制,可参考《Elasticsearch 基础介绍及索引原理分析》一文。

此处仅对其核心原理进行简要概述。

Elasticsearch工作原理:

在部署Elasticsearch服务器后,首要步骤是创建索引。ES会对源数据库数据进行预处理,并构建独立的索引结构数据。

为了便于理解:

假设商品表中包含id、名称、副标题、价格、图片链接、评论数及上架状态等字段。

用户通常通过名称或副标题进行搜索,因此这两个字段需要建立索引(id作为主键自然也在索引范围内)。但后端返回给前端的数据不仅限于搜索字段,还需包含价格等详细信息。若仅返回名称或副标题,无法满足业务需求,因此必须指定需要返回的字段。

在创建索引库时,Elasticsearch服务端会根据配置的索引字段(如名称、副标题、id)和返回字段(如价格等)同步数据至ES索引库。之所以选择ES,主要因其检索速度极快。关于ES为何如此高效,可参考前述链接中的原理分析。

注意上图所示,Elasticsearch采用C/S架构。接下来介绍服务端的搭建过程。

Elasticsearch服务端搭建

Elasticsearch服务端搭建步骤:

在搭建前需说明,Elasticsearch在建立索引时会进行分词处理。所谓分词,例如将“我今天吃了一个汉堡包”分解为“我”、“今天”、“吃了”、“一个”、“汉堡包”。Elasticsearch对英文分词效果良好,但对中文默认仅按单字分割(如“我”、“今”、“天”等),这严重影响搜索准确性。因此,需要在Elasticsearch服务端集成ik插件以支持中文智能分词。

那么,如何安装带有ik插件的Elasticsearch服务端?

鉴于直接安装较为繁琐,本文采用Docker方式部署(见图示)。

(1)加载Docker镜像:


sudo docker load -i elasticsearch-ik-2.4.6_docker.tar

(2)修改配置文件:

在 elasticsearc-2.4.6/config/elasticsearch.yml 文件的第54行,将 network.host 更改为本机IP地址:


network.host: xxx.xxx.xxx.xxx

若Docker未运行在开发环境本机,可将IP设为 0.0.0.0,表示允许所有IP访问该服务器。

(3)运行容器:


docker run -d -p 9200:9200 --network=host --name=elasticsearch -v /var/elasticsearch-2.4.6/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0

(4)测试Elasticsearch安装是否成功:


curl 'http://xxx.xxx.xxx.xxx:9200/' # IP地址是ElasticSearch的IP

若测试通过,说明Elasticsearch服务器搭建完成。该镜像已集成ik插件,支持中文分词。服务端就绪后,接下来配置客户端。

Haystack客户端对接与配置

使用Haystack对接Elasticsearch客户端:

若在Django项目中直接编写Elasticsearch客户端代码,复杂度较高。借助第三方包Haystack对接Elasticsearch客户端,不仅简化了开发,还提高了系统的可扩展性。未来若更换其他全文搜索引擎(尽管可能性较低),无需修改Django项目中已编写的代码。

(1)安装Haystack和Elasticsearch客户端:


pip install drf-haystack # 因为该项目是用DRF写的前后端分离,所以安装的是drf-haystack。如果不用DRF的话,安装的是django-haystack
pip install elasticsearch==2.4.1

(2)配置Haystack:

1. 注册应用:


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

2. 在项目配置文件中配置Haystack:


  # 配置haystack全文检索框架
  HAYSTACK_CONNECTIONS = {
    'default': {
      'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
      # 此处为elasticsearch运行的服务器ip地址,端口号默认为9200
      'URL': 'http://xxx.xxx.xxx.xxx:9200/', 
      # 指定elasticsearch建立的索引库的名称
      'INDEX_NAME': 'meiduo', 
    },
  }
  # 当添加、修改、删除数据时,自动更新索引
  HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

(3)创建索引类:

创建索引类的目的是指定需保存的字段,Elasticsearch服务器会将MySQL中这些字段的数据同步,以便查询时返回。


# goods(应用名)/search_indexes.py  # search_indexes名字不能改,固定
from haystack import indexes
from .models import SKU

class SKUIndex(indexes.SearchIndex, indexes.Indexable):
  """
  SKU索引类
  """  # text表示被查询的字段,用户搜索的是这些字段的值,具体被索引的字段写在另一个文件里。
  text = indexes.CharField(document=True, use_template=True)

  # 保存在索引库中的字段
  id = indexes.IntegerField(model_attr='id')
  name = indexes.CharField(model_attr='name')
  price = indexes.DecimalField(model_attr='price')
  default_image_url = indexes.CharField(model_attr='default_image_url')
  comments = indexes.IntegerField(model_attr='comments')

  def get_model(self):
    """返回建立索引的模型类"""
    return SKU

  def index_queryset(self, using=None):
    """返回要建立索引的数据查询集"""
    return self.get_model().objects.filter(is_launched=True)

(4)指定被索引的字段:


# templates/search/indexes/goods(应用名)/sku_text.txt  # 路径和名字是固定的
{{ object.name }}
{{ object.caption }}
{{ object.id }}

(5)生成索引库:


python manage.py rebuild_index

此时,索引库已成功生成。接下来需在后端接收用户提交的查询参数,并返回相应字段。

后端逻辑完善与测试

完善后端逻辑:

前文编写的SKUIndex可视为DRF中的Model类,接下来需编写序列化器、视图并注册路由。

(1)Haystack序列化器类:


from drf_haystack.serializers import HaystackSerializer

class SKUIndexSerializer(HaystackSerializer):
  """
  SKU索引结果数据序列化器
  """
  class Meta:
    index_classes = [SKUIndex]
    fields = ('text', 'id', 'name', 'price', 'default_image_url', 'comments')

(2)Haystack视图:


from drf_haystack.viewsets import HaystackViewSet

class SKUSearchViewSet(HaystackViewSet):  # HaystackViewSet继承了RetrieveModelMixin, ListModelMixin, ViewSetMixin, HaystackGenericAPIView,所以可以查一条或多条数据
  """
  SKU搜索
  HaystackViewSet: 查一条,查多条
  """
  index_models = [SKU]
  serializer_class = SKUIndexSerializer

(3)注册路由:


router = DefaultRouter()
router.register('skus/search', views.SKUSearchViewSet, base_name='skus_search')
...
urlpatterns += router.urls

(4)访问测试:127.0.0.1:8080/skus/search/?text=Apple

即可查询出包含“Apple”的数据。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表