Python Web开发:基于django-haystack的全文高级搜索

来自CloudWiki
跳转至: 导航搜索

背景

Python Web开发:基于模糊查询的新闻标题搜索中实现了新闻标题搜索功能,

对于一个搜索引擎来说,至少应该能够根据用户搜索关键词对全文进行搜索并且能够将关键词高亮显示。

本节使用django-haystack库实现这些更高级的特性。

django-haystack简介

django-haystack是一个专门提供搜索功能的Django第三方应用,它支持Solr、ElasticSearch、Whoosh、Xapian等多种引擎

准备工作

  • Whoosh:一个由纯Python实现的全文搜索引擎,
  • jieba :中文分词库,由于Whool自带的是英文分词,对中文的支持不好,因此用jiaba替换内置分词组件

pip uninstall django

pip install django==2.2 (由于安装的组件需要django版本 >=2.2 ,所以把原先的django2.1.5的版本先卸了,安装新版本)

pip install whoosh django-haystack jiaba


实训步骤

项目配置

settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'homeApp',     # 添加"首页"应用
    'aboutApp',    # 添加"公司简介"应用
    'contactApp',  # 添加"人才招聘"应用
    'newsApp',     # 添加"新闻动态"应用
    'productsApp', # 添加"产品中心"应用
    'serviceApp',  # 添加"服务支持"应用
    'scienceApp',  # 添加"科研基地"应用
    'DjangoUeditor',#添加富文本应用
    'haystack',#添加搜索应用
]

然后在settings.py文件末尾添加如下配置项:

# 新闻搜索配置django-haystack
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'newsApp.whoosh_backend.WhooshEngine',
        'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
    },
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE  =  10
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signal.RealtimeSignalProcessor'
  • HAYSTACK_CONNECTIONS.ENGINE: 指定了使用的搜索引擎
  • PATH:索引文件存放的位置
  • HAYSTACK_SEARCH_RESULTS_PER_PAGE:每页显示数目
  • HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signal.RealtimeSignalProcessor' 什么时候更新索引,这里是实时更新。

建立索引

在newsApp下建立文件search_indexs.py:

from haystack import indexes
from .models import MyNew


class MyNewIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)

    def get_model(self):
        return MyNew

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

django-haystack 规定如果对某个APP进行全文检索,就要在App下创建一个search_indexes.py文件,然后创建一个XXIndex类(XX是模型的名称)

每个索引有且只能有一个字段为document=True ,一般约定此字段名为text。设置参数use_template=True的意思是允许使用数据模板去建立索引文件。

数据模板从哪里定义呢?

 hengDaProject\templates\search\indexes\newsApp下新建一个MyNew_text.txt的文件

文件内容如下:

{{ object.title }}
{{ object.description }}

这样就可以通过title和description来检索MyNew数据了。检索时对这两个字段做全文检索匹配


配置路由和链接

项目全局urls.py,添加搜索路由

urlpatterns = [
    path('admin/', admin.site.urls),                    # 管理员
    path('', home, name='home'),                        # 首页
    path('aboutApp/', include('aboutApp.urls')),        # 公司简介
    path('contactApp/', include('contactApp.urls')),    # 人才招聘
    path('newsApp/', include('newsApp.urls')),          # 新闻动态
    path('productsApp/', include('productsApp.urls')),  # 产品中心
    path('scienceApp/', include('scienceApp.urls')),    # 科研基地
    path('serviceApp/', include('serviceApp.urls')),    # 服务支持
    path('ueditor/',include('DjangoUeditor.urls')),   #富文本
    path('search/', include('haystack.urls')),        # 搜索应用 
]

修改newList.html 搜索表单的跳转链接:

<div class="model-details-title">
                {{newName}}
                <div class="col-md-7 hidden-xs model-details-title-search">
                    <form method="get" action="{% url 'haystack_search' %}">
                        {% csrf_token %}
                        <div class="input-group">
                            <input type="text" name="q" class="form-control" placeholder="请输入关键词" required />
                            <span class="input-group-btn">
                                <input type="submit" class="btn btn-default" value="查询" />
                            </span>
                        </div>
                    </form>
                </div>
            </div>

1.action改为"{% url 'haystack_search' %}">

2. 关键词输入框name改为q,以和django-haystack兼容

建立网页模板

haystack_search视图函数会将搜索结果传至根目录下templates\search\search.html文件

该文件源码如下:

{% extends "base.html" %}
{% load staticfiles %}
{% load highlight %}
{% block title %}
新闻搜索
{% endblock %}
{% block content %}
<link href="{% static 'css/news.css' %}" rel="stylesheet">
<!-- 广告横幅 -->
<div class="container-fluid">
    <div class="row">
        <img class="img-responsive model-img" src="{% static 'img/new.jpg' %}">
    </div>
</div>
<!-- 主体内容 -->
<div class="container">
    <div class="row row-3">
        <div class="model-details-title">
            关于“{{query}}”的搜索结果
            <div class="col-md-7 hidden-xs model-details-title-search">
                <form method="get" id="searchform" action="{% url 'haystack_search' %}">
                    {% csrf_token %}
                    <div class="input-group">
                        <input type="text" name="q" class="form-control" placeholder="请输入关键词" required />
                        <span class="input-group-btn">
                            <input type="submit" class="btn btn-default" value="查询" />
                        </span>
                    </div>
                </form>
            </div>
        </div>
        <div class="model-details">
            {% for result in page.object_list %}
            <div class="news-model">
                <img src="{% static 'img/newsicon.gif' %}">
                <a href="{% url 'newsApp:newDetail' result.object.id %}">
                    <b>{{result.object.title}}</b>
                </a>
                <span>【{{result.object.publishDate|date:"Y-m-d"}}】</span>
                <!-- 添加新闻简要说明 -->
                <p class="news-search-model">
                    {% highlight result.object.description with query %}
                </p>
            </div>
            {% empty %}
            <p>没有找到相关新闻</p>
            {% endfor %}
        </div>
        {% if page.has_previous or page.has_next %}
        <div>
            {% if page.has_previous %}
            <a href="?q={{ query }}&page={{ page.previous_page_number }}">
                {% endif %}« 上一页{% if page.has_previous %}</a>{% endif %}
            |
            {% if page.has_next %}
            <a href="?q={{ query }}&page={{ page.next_page_number }}">
                {% endif %}下一页
                »{% if page.has_next %}</a>{% endif %}
        </div>
        {% endif %}
    </div>
</div>
{% endblock %}
  • haystack对搜索结果做了分页,传给模板的变量是一个page对象,所以从page中取出这一页对应的搜索结果{% for result in page.object_list %}
  • query代表搜索词,用{% highlight result.object.description with query %}做了高亮处理
  • 在news.css中定义样式:
/* 全文搜索样式 */
.news-search-model span{
	float:none;
}
.news-model a span{
	float: none;
}
.highlighted {
	color: red;
}

配置搜索引擎

现在我们需要导入搜索引擎(whoosh)的支持文件,并修改分词方式为jieba分词(因为自带分词对中文支持不好)

C:\Users\maxin\AppData\Local\Programs\Python\Python37\Lib\site-packages\haystack\backends 中找到whoosh_backends,py ,并将其复制到newsApp文件夹下,

前面settings指定的就是这个文件,

找到如下一行代码:

schema_fields[field_class.index_fieldname] = TEXT(
                    stored=True,
                    analyzer=StemmingAnalyzer(),
                    field_boost=field_class.boost,
                    sortable=True,
                )

将其中analyzer=StemmingAnalyzer(),改为analyzer=ChineseAnalyzer(),

为了使用它,需要在文件顶部引入:

from jieba.analyse import ChineseAnalyzer

至此,完成了整个搜索引擎的搭建,运行下述命令来重建索引 完成内容索引

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 4 新闻
Building prefix dict from the default dictionary ...
Dumping model to file cache C:\Users\maxin\AppData\Local\Temp\jieba.cache
Loading model cost 0.830 seconds.
Prefix dict has been built successfully.

python manage.py runserver