二十四. 新闻搜索 2021-04-30

二十四. 新闻搜索

注:该篇文章接上一篇 二十三.新闻详情页
在上一篇文章我们实现了资讯模块中的新闻详情页功能,在这一章实现点新闻搜索.

一、功能需求分析

思考,如果我们要做一个通过关键词搜索文章的功能,需要搜索哪些字段,以及使用什么技术方案呢?

搜索字段:

  1. 标题
  2. 内容
  3. 作者

技术方案:

  1. mysql的模糊查询 %like%
    1. 优点:实现起来简单
    2. 缺点:数据量比较大的情况下,查询效率极低
  2. 全文检索引擎
    1. 优点:专业的全文检索引擎,效率高
    2. 缺点:实现起来比较复杂

本项目选择使用过全文检索引擎。自行实现django框架和全文检索引擎的代码比较麻烦,抱着不重复造轮子的原则,这里我们选用django的第三方包djangohaystack。它支持多种全文检索引擎,本项目选择最流行的全文检索引擎之一elasticsearch

二、elasticsearch介绍

elasticsearch 原理;http://developer.51cto.com/art/201904/594615.html

三、docker介绍

1.docker介绍与安装

  • 介绍

    1. 什么是docker?

      • 简化创建,部署,运行应用程序的一个工具
      • 打包应用程序所需的库和依赖环境
      • 精简的虚拟机
        在这里插入图片描述
    2. 为什么使用docker?
      在这里插入图片描述

      流行,方便,强大

    3. docker vs 虚拟机
      在这里插入图片描述
      在这里插入图片描述

    4. docker架构
      在这里插入图片描述

    • 架构
      • 客户端
      • 守护进程
      • 仓库
    • docker 对象
      • 镜像 精简的linux
      • 容器
      • 服务
    • docker Hub wcfdehao
  • 安装
    官方安装文档

lsb_release -a   # 查看系统信息
uname -a		# 查看位数

在这里插入图片描述

  ubuntu下安装    
  
如果是第一次安装,你需要先添加docker的源然后再安装
  
1. 更新包
  
  ```bash
  $ sudo apt-get update      
  #如果更新失败使用
进行如下操作:

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 6AF0E1940624A220
1
#此处6AF0E1940624A220需要是上面错误提示的那些key
错几个key就这么加几回
  1. 安装证书
$ sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
  1. 添加docker的官方GPGkey
  $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
4. 添加docker源
  $ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

安装 docker ce

  1. 更新包索引
 $ sudo apt-get update
  1. 安装docker(这个可能会很久)
  $ sudo apt-get install docker-ce
  1. 检测是否安装成功
$ sudo docker run hello-world

安装成功会出现如下输出:

 Hello from Docker!
      This message shows that your installation appears to be working correctly.
      
      To generate this message, Docker took the following steps:
     1. The Docker client contacted the Docker daemon.
       2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
        (amd64)
       3. The Docker daemon created a new container from that image which runs the
          executable that produces the output you are currently reading.
       4. The Docker daemon streamed that output to the Docker client, which sent it
          to your terminal.
      
      To try something more ambitious, you can run an Ubuntu container with:
       $ docker run -it ubuntu bash
      
      Share images, automate workflows, and more with a free Docker ID:
       https://hub.docker.com/
      
      For more examples and ideas, visit:
       https://docs.docker.com/get-started/

为了方便使用,不用sudo就可以运行docker命令,安装好docker后再命令行输入如下命令:

$ sudo usermod -aG docker $USER

运行正常后,重新连接即可。

四、搜索功能环境搭建

1.在docker中安装elasticsearch

  1. 获取镜像
   # 注意:因为haystack目前支持的elasticsearch版本为 1.x和2.x
   # 所以这里选择2.4.6
   $ sudo docker pull elasticsearch:2.4.6
   
   sudo docker images  查看当前镜像

在这里插入图片描述
在这里插入图片描述

  1. 安装中文分词插件
    可以创建容器之后再安装插件,为了后面部署方便,我们创建镜像。elasticsearch的中文分词插件是elasticsearch-ik,国人开发,github地址
    根据文档介绍,2.4.6版本对应的ik是1.10.16
    在这里插入图片描述

因为直接使用elasticsearch的plugin命令安装会报错,所以通过下载后解压到相应文件夹的方式安装。
在这里插入图片描述

a.下载es-ik后,将其解压到名为ik的文件夹

 ~$ unzip elasticsearch-analysis-ik-1.10.6.zip -d ./ik

b.在ik所在文件下创建名为Dockerfile的文件,内容如下

 # dockerfile
   FROM	elasticsearch:2.4.6
   MAINTAINER	Fisher "xinlan@tanzhou.com"
   ADD 	./ik/ /usr/share/elasticsearch/plugins/ik/	

然后运行命令

 ~$ sudo docker build -t xinlan/els-ik:2.4.6 .

如果出现下面的错误是因为没有带sudo

 ~$ docker build -t xinlan/els-ik:2.4.6 .
   error checking context: 'no permission to read from '/home/wcf/.viminfo''.

运行成功后,会在你的docker中创建一个新的镜像

~$ docker images
   REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
   xinlan/els-ik       2.4.6               ecf93deefe2b        26 minutes ago      489MB
   elasticsearch       2.4.6               5e9d896dc62c        10 months ago       479MB

如果上面的步骤搞不定请直接下载如下镜像

  $ sudo docker image pull wcfdehao/els-ik:2.4.6

在这里插入图片描述

  1. 创建容器
    利用上面创建好的镜像创建一个容器。为了能够进行设置elasticsearch,通过卷挂载的方式创建容器。
    将提供给大家的es配置文件elasticsearch.zip拷贝到家目录下,然后解压
    链接:https://pan.baidu.com/s/1sSkoCYR3lDJCKyol7OlQ-Q
    提取码:g6fi
    复制这段内容后打开百度网盘手机App,操作更方便哦
    在这里插入图片描述
 # 在xshell中使用rz命令将elasticsearch.zip文件传到虚拟机的家目录中
   #然后在家目录中解压
   ~$ unzip elasticsearch.zip

ps

然后运行下面的命令创建容器

# 根据上面创建的镜像创建容器,需要将/home/wcf/elasticsearch/config配置文件路径修改为你自己的路径
   # 将 镜像名hhh/els-ik:2.4.6改成你的镜像名 
   # docker run -dti --network=host --name es-ik -v /home/pyvip/elasticsearch/config:/usr/share/elasticsearch/config wcfdehao/els-ik:2.4.6
   ~$ docker run -dti --network=host --name es-ik -v /home/wcf/elasticsearch/config:/usr/share/elasticsearch/config wcfdehao/els-ik:2.4.6
   # 查看是否创建成功,如果没有结果,说明创建失败检查步骤和配置
   ~$ docker ps
   CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
   61c42c36a8f2       wcfdehao/els-ik:2.4.6   "/docker-entrypoint.…"   29 minutes ago      Up 
   28 minutes                           es-ik

在这里插入图片描述
通过查看cat elasticsearch.yml找到端口和ip,配置的时候按照这端口和ip配置连接在这里插入图片描述
运行镜像显示已经生成容器,无法在生成
xia
还没有运行起来
在这里插入图片描述
通过命令 sudo docker ps -a 查看所有的镜像
查看后发现有wcfdehao/els-ik:2.4.6 镜像,状态为未启动
在这里插入图片描述

通过镜像的id来启动镜像 sudo docker start bb3426e76dd1
在这里插入图片描述
在这里插入图片描述

最后运行curl命令检测es是否正常

   ~$ curl http://127.0.0.1:9200
   {
     "name" : "Shard",
     "cluster_name" : "elasticsearch",
     "cluster_uuid" : "Pq6BQQhTQN6q6ML6ThPlbw",
     "version" : {
       "number" : "2.4.6",
       "build_hash" : "5376dca9f70f3abef96a77f4bb22720ace8240fd",
       "build_timestamp" : "2017-07-18T12:17:44Z",
       "build_snapshot" : false,
       "lucene_version" : "5.5.4"
     },
     "tagline" : "You Know, for Search"
   }

2.安装djangohaystack

官方文档

  1. 安装
   # 安装djangohaystack
   # 使用的是当期最新版本 
   pip install django-haystack

在这里插入图片描述

  1. 配置文件
   # 将Haystack添加到`INSTALLED_APPS`中
   # settings.py
   INSTALLED_APPS = [
       'django.contrib.admin',
       'django.contrib.auth',
       'django.contrib.contenttypes',
       'django.contrib.sessions',
       'django.contrib.messages',
       'django.contrib.staticfiles',
       'haystack',
       'user',
       'news',
       'doc',
       'course',
       'verification'
   ]
 # 配置搜索引擎
   # 在settings.py中添加如下设置
   # 全文搜索引擎haystack 配置
   # 不同的搜索引擎,配置不同,详情见官方文档
   HAYSTACK_CONNECTIONS = {
       'default': {
           'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
           'URL': 'http://0.0.0.0:9200/',    # 此处为elasticsearch运行的服务器ip地址和端口
           'INDEX_NAME': 'DBogpython',           # 指定elasticserach建立的索引库名称
       },
   }
   
   # 搜索结果每页显示数量
   HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
   # 实时更新index    保持和mysql 中的数据一致
   HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
  1. 安装elasticsearch-py
    haystack操作es还需要python的es驱动。兼容性见官网
    在这里插入图片描述

    根据官网,选择2.4.1版本

 pip install elasticsearch==2.4.1

至此,环境搭建完成。相对应的es,es-ik,haystack,es-python的版本请保持一致。

五、新闻搜索功能实现

1.业务流程分析

  • 判断是否传递查询参数q
  • 如果没有传递q,则直接返回热门新闻数据
  • 如果有传递,则返回查询结果
  • 分页

2. 接口设计

  1. 接口说明:
类目说明
请求方法GET
url定义/news/search/
参数格式查询参数
  1. 参数说明:
参数名类型是否必须描述
q字符串查询的关键字
page整数页码
  1. 返回结果:
    搜索页面html

3.后端代码

  1. 创建haystack数据模型
    在apps/news/目录下创建search_indexes.py文件,注意文件名必须使用search_indexes.py,代码如下:
 # !/usr/bin/env python
   # -*- coding:utf-8 -*-
   from haystack import indexes
   from .models import News
   
   
   class NewsIndex(indexes.SearchIndex, indexes.Indexable):
       """
       这个模型的作用类似django的模型,它告诉haystack哪些数据会被
       放进查询回的模型对象中,以及通过哪些字段进行索引和查询
       """
       # 这字段必须这么写,用来告诉haystack和搜索引擎要索引哪些字段
       text = indexes.CharField(document=True, use_template=True)
       id = indexes.CharField(model_attr='id')
       title = indexes.CharField(model_attr='title')
       digest = indexes.CharField(model_attr='digest')
       content = indexes.CharField(model_attr='content')
       image_url = indexes.CharField(model_attr='image_url')
   
       def get_model(self):
           """
           返回建立索引的模型
           :return:
           """
           return News
   
       def index_queryset(self, using=None):
           """
           返回要建立索引的数据查询集
           :param using:
           :return:
           """
           return self.get_model().objects.filter(is_delete=False)
  1. 创建索引数据模板
    根据上面创建的模型中的第一个text字段中的use_template=True参数,还需要创建一个索引数据模板,用来告诉搜索引擎需要索引哪些字段。
    在templates中创建文件search/indexes/yourappname/modelname_text.txt,所以本项目需要创建search/indexes/news/news_text.txt,文件内容如下:
   {{ object.title }}
   {{ object.digest }}
   {{ object.content }}
   {{ object.author.username }}
  1. 创建索引
    按上面的步骤配置好后,就可以运行haystack的命令创建索引了
 ~$ python manage.py rebuild_index
  1. 视图代码

在news/views.py中添加如下视图

from haystack.generic_views import SearchView

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

    # 重写get请求,如果请求参数q为空,返回模型News的热门新闻数据
    # 否则根据参数q搜索相关数据
    def get(self, request, *args, **kwargs):
        query = request.GET.get('q')
        if not query:
            # 显示热门新闻
            hot_news = HotNews.objects.select_related('news__tag').only('news__title', 'news__image_url', 'news_id',
                                                                        'news__tag__name').filter(
                is_delete=False).order_by('priority', '-news__clicks')
            paginator = Paginator(hot_news, settings.HAYSTACK_SEARCH_RESULTS_PER_PAGE)
            try:  
                # 分页
                page = paginator.get_page(int(request.GET.get('page')))
            except Exception as e:
                page = paginator.get_page(1)
			# 返回数据
            return render(request, 'news/search.html', context={
                'page': page,
                'paginator': paginator,
                'query': query
            })
        else:
            # 搜索
            return super().get(request, *args, **kwargs)

    def get_context_data(self, *args, **kwargs):
        """
        在context中添加page变量
        :param args: 
        :param kwargs: 
        :return: 
        """
        context = super().get_context_data(*args, **kwargs)
        if context['page_obj']:
            context['page'] = context['page_obj']
        return context
  1. 路由
    在news/urls.py中添加如下路由
  path('news/search/', views.NewsSearchView.as_view(), name='news_search')

4.前端代码

  1. 自定义过滤器

在news/templatetags/news_template_filters.py中定义一个处理分页的过滤器

# !/usr/bin/env python
# -*- coding:utf-8 -*-
# create_time: 2019/7/14
# Author = '心蓝'
from django import template

register = template.Library()


@register.filter
def page_bar(page):
    page_list = []
    if page.number != 1:
        page_list.append(1)
    if page.number - 3 > 1:
        page_list.append('...')
    if page.number - 2 > 1:
        page_list.append(page.number - 2)
    if page.number - 1 > 1:
        page_list.append(page.number - 1)
    page_list.append(page.number)
    if page.paginator.num_pages > page.number + 1:
        page_list.append(page.number + 1)
    if page.paginator.num_pages > page.number + 2:
        page_list.append(page.number + 2)
    if page.paginator.num_pages > page.number + 3:
        page_list.append('...')
    if page.paginator.num_pages != page.number:
        page_list.append(page.paginator.num_pages)
    return page_list
  1. 前端html代码
{% extends 'base/base.html' %}
{% load static %}
{% load news_customer_filters %}
{% block title %}新闻搜索{% endblock %}
{% block link %}
    <link rel="stylesheet" href="{% static 'css/news/search.css' %}">
{% endblock %}

{% block main_contain %}
    <!-- main-contain start  -->
    <div class="main-contain ">
        <!-- search-box start -->
        <div class="search-box">
            <form action="" style="display: inline-flex;">

                <input type="search" placeholder="请输入要搜索的内容" name="q" class="search-control">


                <input type="submit" value="搜索" class="search-btn">
            </form>
            <!-- 可以用浮动 垂直对齐 以及 flex  -->
        </div>
        <!-- search-box end -->
        <!-- content start -->
        <div class="content">
            {% if query %}
                <!-- search-list start -->
                <div class="search-result-list">
                    <h2 class="search-result-title">搜索结果 <span>{{ page.paginator.num_pages|default:0 }}</span></h2>
                    <ul class="news-list">
                        {% load highlight %}
                        {% for news in page.object_list %}
                            <li class="news-item clearfix">
                                <a href="{% url 'news:news_detail' news.id %}" class="news-thumbnail" target="_blank"><img src="{{ news.image_url }}" alt=""></a>
                                <div class="news-content">
                                    <h4 class="news-title">
                                        <a href="{% url 'news:news_detail' news.id %}">{% highlight news.title with query %}</a>
                                    </h4>
                                    <p class="news-details">{{ news.digest }}</p>
                                    <div class="news-other">
                                        <span class="news-type">{{ news.object.tag.name }}</span>
                                        <span class="news-time">{{ news.object.update_time }}</span>
                                        <span class="news-author">{% highlight news.object.author.username with query %}</span>
                                    </div>
                                </div>

                            </li>
                        {% empty %}
                            <li class="news-item clearfix">
                                <p>没有找到你想要的找的内容.</p>
                            </li>
                        {% endfor %}
                    </ul>
                </div>

                <!-- search-list end -->
            {% else %}
                <!-- news-contain start -->

                <div class="news-contain">
                    <div class="hot-recommend-list">
                        <h2 class="hot-recommend-title">热门推荐</h2>
                        <ul class="news-list">
                            {% for hotnews in page %}
                                <li class="news-item clearfix">
                                    <a href="#" class="news-thumbnail">
                                        <img src="{{ hotnews.news.image_url }}">
                                    </a>
                                    <div class="news-content">
                                        <h4 class="news-title">
                                            <a href="{% url 'news:news_detail' hotnews.news_id %}">{{ hotnews.news.title }}</a>
                                        </h4>
                                        <p class="news-details">{{ hotnews.news.digest }}</p>
                                        <div class="news-other">
                                            <span class="news-type">{{ hotnews.news.tag.name }}</span>
                                            <span class="news-time">{{ hotnews.update_time }}</span>
                                            <span class="news-author">{{ hotnews.news.author.username }}</span>
                                        </div>
                                    </div>
                                </li>
                            {% endfor %}


                        </ul>
                    </div>
                </div>


                <!-- news-contain end -->
            {% endif %}
            <!-- Pagination start-->
            <div class="page-box" id="pages">
                <div class="pagebar" id="pageBar">
                    <a class="al">{{ page.paginator.count|default:0 }}</a>
                    <!-- prev page start-->
                    {% if page.has_previous %}
                        {% if query %}
                            <a href="{% url 'news:news_search' %}?q={{ query }}&page={{ page.previous_page_number }}"
                               class="prev">上一页</a>
                        {% else %}
                            <a href="{% url 'news:news_search' %}?page={{ page.previous_page_number }}"
                               class="prev">上一页</a>
                        {% endif %}
                    {% endif %}
                    <!-- prev page end-->

                    <!-- page bar start-->
                {% if page.has_previous or page.has_next %}
                    {% for n in page|page_bar %}
                        {% if query %}
                            {% if n == '...' %}
                            <span class="point">{{ n }}</span>
                            {% else %}
                                {% if n == page.number %}
                                    <span class="sel">{{ n }}</span>
                                {% else %}
                                    <a href="{% url 'news:news_search' %}?page={{ n }}&q={{ query }}">{{ n }}</a>
                                {% endif %}
                            {% endif %}
                        {% else %}
                            {% if n == '...' %}
                                <span class="point">{{ n }}</span>
                            {% else %}
                                {% if n == page.number %}
                                    <span class="sel">{{ n }}</span>
                                {% else %}
                                    <a href="{% url 'news:news_search' %}?page={{ n }}">{{ n }}</a>
                                {% endif %}
                            {% endif %}
                        {% endif %}
                    {% endfor %}
                {% endif %}
                    <!-- page bar end-->

                    <!-- next page start-->
                    {% if page.has_next %}
                        {% if query %}
                            <a href="{% url 'news:news_search' %}?q={{ query }}&page={{ page.next_page_number }}"
                               class="prev">下一页</a>
                        {% else %}
                            <a href="{% url 'news:news_search' %}?page={{ page.next_page_number }}"
                               class="prev">下一页</a>
                        {% endif %}
                    {% endif %}
                    <!-- next page end-->


                </div>
            </div>
            <!-- Pagination end-->
        </div>
        <!-- content end -->
    </div>
    <!-- main-contain  end -->
{% endblock %}
  1. css代码

修改static/css/news/search.css如下:

/* ================= main start ================= */
#main {
    margin-top: 25px;
    min-height: 700px;
}
/* ========= main-contain start ============ */
#main .main-contain {
    width: 800px;
    float: left;
    background: #fff;
}

/* ===  search-box start === */
.main-contain .search-box {
    padding: 40px 50px;
    width: 700px;
    box-shadow: 1px 2px rgba(0,0,0,.1);
    display: inline-flex;
}
.main-contain .search-box .search-control {
    width: 600px;
    height: 40px;
    border-radius: 20px 0 0 20px;
    border: 1px solid #ddd;
    border-right: none;
    padding-left: 0.88em;
    font-size: 20px;
}
.main-contain .search-box .search-btn {
    width: 100px;
    height: 40px;
    border: 1px solid red;
    background: red;
    color: #fff;
    font-size: 20px;
    border-radius:  0 20px 20px 0;
    cursor: pointer;
}
/* ===  search-box end === */

/* === content start === */
/* == search-list start == */
.content .search-result-list {
    padding-top: 20px;
}
.content .search-result-list .search-result-title {
    padding-left: 20px;
    font-size: 20px;
    line-height: 26px;
}
.content .search-result-list .search-result-title span {
    font-weight: 700;
    color: #ff6620;
}
/* == search-list end == */
/* == news-contain start == */
.content .news-contain .hot-recommend-list {
    padding-top: 20px;
}
.hot-recommend-list .hot-recommend-title {
    padding-left: 20px;
    font-size: 20px;
    line-height: 26px;
}
.content .news-contain li {
    border-bottom: 1px solid #ededed;
}
.news-list .news-item {
    padding: 20px;
}
.news-list .news-item .news-thumbnail {
    float: left;
    width: 224px;
    height: 160px;
    margin-right: 30px;
    overflow: hidden;
}
.news-item .news-thumbnail img {
    width: 100%;
    height: 100%;
    transition: all 0.3s ease-out;
}
.news-item .news-thumbnail:hover img {
    transform: scale(1.1);
    transition: all 0.3s ease-in;
}
.news-list .news-item .news-content {
    width: 500px;
    height: 170px;
    float: right;
    color: #878787;
    font-size: 14px;
}
.news-item .news-content .news-title{
    color: #212121;
    font-size: 22px;
    height: 52px;
    line-height: 26px;
    transition:all 0.3s ease-out;
}
.news-item .news-content .news-title:hover {
    color: #5b86db;
    transition:all 0.3s ease-in;
}
.news-item .news-content .news-details {
    height: 44px;
    line-height: 22px;
    margin-top: 19px;
    text-align: justify;
}
.news-item .news-content .news-other {
    margin-top: 30px;
}
.news-content .news-other .news-type {
    color: #5b86db;
}
.news-content .news-other .news-author {
    float: right;
    margin-right: 15px;
}
.news-content .news-other .news-time {
    float: right;
}
/* === current index start === */
#pages {
	padding: 32px 0 10px;
}

.page-box {
	text-align: center;
    /*font-size: 14px;*/
}

#pages a.prev, a.next {
	width: 56px;
	padding: 0
}

#pages a {
	display: inline-block;
	height: 26px;
	line-height: 26px;
	background: #fff;
	border: 1px solid #e3e3e3;
	text-align: center;
	color: #333;
	padding: 0 10px
}

#pages .sel {
	display: inline-block;
	height: 26px;
	line-height: 26px;
	background: #0093E9;
	border: 1px solid #0093E9;
	color: #fff;
	text-align: center;
	padding: 0 10px
}
#pages .point {
	display: inline-block;
	height: 26px;
	line-height: 26px;
	background: #fff;
	border: 1px solid #e3e3e3;
	text-align: center;
	color: #333;
	padding: 0 10px
}
.highlighted {
    font-weight: 700;
    color: #ff6620;
}
/* === current index end === */
/* === content end === */
/* ================= main end ================= */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值