ElasticSearch和whoosh实现项目中搜索功能

搜索

自己最近总结了一下项目中实现过的搜索功能,接下来就通过具体的python项目来进行说明。

一.单表搜索

实现对于特定表中的某些字段的模糊搜索匹配,比如需要根据关键字搜索教师表中的授课教师或者所教课程的信息:

在这里插入图片描述
可以通过数据库查询语句直接实现:

# 根据搜索词search_data查询结果
Teacher.objects.filter(Q(teacher__contains=search_data) | Q(course__contains=search_data))

优点: 使用简单方便
缺点: 模糊查询效率低,数据量较大时会变得异常吃力


二.全文搜索

既然数据库模糊匹配在数据量庞大时不能高效实现全文搜索,那我们就要需要选择合适的搜索引擎来实现,现在主流的搜索引擎大概就是:Lucene,Solr,ElasticSearch。python项目中还会经常用到一个纯python实现的全文搜索引擎whoosh,更加小巧简单。

在django项目中实现全文搜索,可以使用搜索框架haystack来实现,
haystack可以方便地在django中直接添加搜索功能,无需关注索引建立、搜索解析等细节问题。

下面就分别使用whoosh和ElasticSearch来实现项目中的全文搜索。


1.haystack+whoosh+Jieba

1.1 配置
  1. 安装依赖包
pip install whoosh/django-haystack/jieba

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

  1. 注册haystack 到Django的 INSTALLED_APPS
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # haystack注册
    'haystack',
]
  1. settings中增加搜索引擎配置
import os
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
        # PATH为Whoosh 索引文件的存放文件夹
        'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
    },
}
1.2 创建索引
  1. 如果你想针对某个app例如courses做全文检索,则必须在blog的目录下面建立search_indexes.py文件,文件名不能修改
    在这里插入图片描述
from haystack import indexes
from .models import Course

class CourseIndex(indexes.SearchIndex, indexes.Indexable):
       # 这字段必须这么写,用来告诉haystack和搜索引擎要索引哪些字段
       text = indexes.CharField(document=True, use_template=True)
       # 模型字段,打包数据
       id = indexes.CharField(model_attr='id')
       course = indexes.CharField(model_attr='course ')
       teacher = indexes.CharField(model_attr='teacher')
       content = indexes.CharField(model_attr='content')
       image_url = indexes.CharField(model_attr='image_url')

    def get_model(self):
        # 重载get_model方法,必须要有!
        return Course

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

创建索引会提供更快速的导航与查找。索引的实现细节是我们不需要关心的,每个索引里面必须有且只能有一个字段为 document=True,这代表haystack 和搜索引擎将使用此字段的内容作为索引进行检索(primary field)。其他的字段只是附属的属性,方便调用,并不作为检索数据

  1. 然后在template下面建立 search/indexes/course
    前两个目录都是固定的,第三层是应用的名称,一一对应上,然后建立course_text.txt,名字是刚刚指定的类名的小写加_text.txt ,这里面就是对应哪个字段建立索引。例如:
{{object.course}}
{{object.teacher}}
1.3 搜索模板

在templates/search/下面,建立一个search.html页面

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style>
        span.highlighted {
            color: red;
        }
    </style>
</head>
<body>
{% load highlight %}
{% if query %}
    <h3>搜索结果如下:</h3>
    {% for result in page.object_list %}
{#        <a href="/{{ result.object.id }}/">{{ result.object.teacher }}</a><br/>#}
        <a href="/{{ result.object.id }}/">{% highlight result.object.teacher with query max_length 2%}</a><br/>
        <p>{{ result.object.course|safe }}</p>
        <p>{% highlight result.course with query %}</p>
    {% empty %}
        <p>啥也没找到</p>
    {% endfor %}

    {% if page.has_previous or page.has_next %}
        <div>
            {% if page.has_previous %}
                <a href="?q={{ query }}&amp;page={{ page.previous_page_number }}">{% endif %}&laquo; 上一页
            {% if page.has_previous %}</a>{% endif %}
            |
            {% if page.has_next %}<a href="?q={{ query }}&amp;page={{ page.next_page_number }}">{% endif %}下一页 &raquo;
            {% if page.has_next %}</a>{% endif %}
        </div>
    {% endif %}
{% endif %}
</body>
</html>

需要注意的是page.object_list实际上是SearchResult对象的列表。这些对象返回索引的所有数据。它们可以通过{{result.object}}来访问。所以{{ result.object.teacher}}实际使用的是数据库中course对象来访问teacher字段的

1.4 配置URL

添加SearchView到URLconf,在URLconf中添加下面一行:

# 前后端不分离url
url(r'^search/', include('haystack.urls')),

# 前后端分离url
path('search/', MySearchView(), name='haystack_search'),
1.5 视图函数

前后端分离后台需要重写SearchView中的create_response方法,用到的时候可以根据需要重写即可。

1.6 使用jieba分词

建立ChineseAnalyzer.py文件
保存在haystack的安装文件夹下,路径如“D:\python3\Lib\site-packages\haystack\backends”

import jieba
from whoosh.analysis import Tokenizer, Token

class ChineseTokenizer(Tokenizer):
    def __call__(self, value, positions=False, chars=False,
                 keeporiginal=False, removestops=True,
                 start_pos=0, start_char=0, mode='', **kwargs):
        t = Token(positions, chars, removestops=removestops, mode=mode,
                  **kwargs)
        seglist = jieba.cut(value, cut_all=True)
        for w in seglist:
            t.original = t.text = w
            t.boost = 1.0
            if positions:
                t.pos = start_pos + value.find(w)
            if chars:
                t.startchar = start_char + value.find(w)
                t.endchar = start_char + value.find(w) + len(w)
            yield t

def ChineseAnalyzer():
    return ChineseTokenizer()

复制whoosh_backend.py文件,改名为whoosh_cn_backend.py

from .ChineseAnalyzer import ChineseAnalyzer 
# 查找
analyzer=StemmingAnalyzer()
# 改为
analyzer=ChineseAnalyzer()

2.haystack+ES

2.1 环境配置

下载elasticsearch和其依赖包

pip install django-haystack/elasticsearch

然后在settings中增加搜索引擎配置

HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://127.0.0.1:9200/',    # 此处为elasticsearch运行的服务器ip地址和端口
        'INDEX_NAME': 'course',           # 指定elasticserach建立的索引库名称
    },
}

# 搜索结果每页显示数量
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
# 实时更新index
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
2.2 创建索引

和whoosh的步骤一样

2.3 创建索引数据模板

和whoosh的步骤一样

3.4 配置URL

在course/urls.py中添加如下路由

path('course/search/', views.CourseSearchView.as_view(), name='course_search')
3.5 视图函数
from haystack.generic_views import SearchView
from .models import Course

class CourseSearchView(SearchView):
    """
    新闻搜索视图
    """
    # 设置搜索模板文件
    template_name = 'course/search.html'

    # 否则根据参数q搜索相关数据
    def get(self, request, *args, **kwargs):
        # 1. 获取查询参数
        query = request.GET.get('q')
        # 2. 如果没有查询参数
        if not query:
            # 获取课程对象
            course_datas = Course.objects.all()
            # 分页, 从配置文件中拿到haystack参数
            paginator = Paginator(course_datas, settings.HAYSTACK_SEARCH_RESULTS_PER_PAGE)
            try:
                # 拿到前端传递的page,
                page = paginator.get_page(int(request.GET.get('page')))
            except Exception as e:
                # 如果出错则返回第一页,保证容错性
                page = paginator.get_page(1)

            return render(request, self.template_name, context={
                'page': page,
                # 'paginator': paginator,
                'query': query
            })
        # 3. 如果有查询参数
        else:
            # 则执行搜索
            return super().get(request, *args, **kwargs)

    def get_context_data(self, *args, **kwargs):
        """
        在context中添加page变量
        """
        context = super().get_context_data(*args, **kwargs)
        if context['page_obj']:
            # 捕获page_obj,将其赋值到page
            context['page'] = context['page_obj']
        return context

总结

使用sql语句的模糊搜索效率较低,但是使用非常简单,在数据量较小、搜索精准度要求不高的情况下可以使用。

ES和whoosh搜索引擎在项目的配置步骤是相差不多的,ES的性能还是会比whoosh要高一些的,同时也因为whoosh是纯python实现的搜索引擎,在功能和性能上远不如ES。个人更推荐在项目中使用ES+haystack的方式去实现搜索功能。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值