django之搜索引擎功能實現


一、介紹

  我們在瀏覽一些網站時,發現都會有一個搜索框,如果是新聞類網站,就會搜索到包含關鍵字的新聞;如果是課程網站,就會搜索到與關鍵字相關的課程

 

 

 

這個怎么實現呢?不難想到,可以使用數據庫的模糊查詢,對相應的字段就行模糊查詢,如果查詢到就返回對應的數據行,展示在前端,但是數據庫的模糊查詢太慢了,下面介紹一種技術,用於實現這樣的網站搜索引擎的功能。

二、搜索引擎原理

  在Elasticsearch中存儲數據的行為就叫做索引(indexing),不過在索引之前,我們需要明確數據應該存儲在哪里。
在Elasticsearch中,文檔歸屬於一種類型(type),而這些類型存在於索引(index)中

  Elasticsearch集群可以包含多個索引(indices)(數據庫),每一個索引可以包含多個類型(types)(表),每一個
類型包含多個文檔(documents)(行),然后每個文檔包含多個字段(Fields)(列)。

  # 「索引」含義的區分

  1,索引(名詞) 如上文所述,一個索引(index)就像是傳統關系數據庫中的數據庫,它是相關文檔存儲的地方,index的復數是indices 或indexes。
  2,索引(動詞) 「索引一個文檔」表示把一個文檔存儲到索引(名詞)里,以便它可以被檢索或者查詢。這很像SQL中的INSERT關鍵字,差別是,如果文檔已經存在,新的文檔將覆蓋舊的文檔。
  3,倒排索引 傳統數據庫為特定列增加一個索引,例如B-Tree索引來加速檢索。Elasticsearch和Lucene使用一種叫做倒排索引(inverted index)的數據結構來達到相同目的。

三、elasticsearch

  • 開源

  • 搜索引擎首選

  • 底層是開源庫Lucene

  • REST API 的操作接口

  搜索引擎在對數據構建索引時,需要進行分詞處理。分詞是指將一句話拆解成多個單字或詞,這些字或詞便是這句話的關鍵詞。Elasticsearch 不支持對中文進行分詞建立索引,需要配合擴展elasticsearch-analysis-ik來實現中文分詞處理。

四、使用docker安裝elasticsearch

  1、獲取鏡像(由於版本問題,使用2.4.6-1.0版本)

docker image pull delron/elasticsearch-ik:2.4.6-1.0

  在虛擬機中的elasticsearch/config/elasticsearch.yml第54行,更改ip地址為0.0.0.0,端口改為9200,默認端口為9200

# network.host: 172.18.168.123
network.host: 0.0.0.0
#
# Set a custom port for HTTP:
#
http.port: 9200

  2、創建docker容器並運行

docker run -dti --network=host --name=elasticsearch -v /home/pyvip/elasticsearch/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0

# 如果容器不穩定切換這條命令創建容器
docker run -dti --name=elasticsearch -p 9200:9200 delron/elasticsearch-ik:2.4.6-1.0

  3、進入項目虛擬環境中,安裝相關包

# 進入項目虛擬環境
workon dj31_env

# 如果安裝報錯,先初始化  pip3 install setuptools_scm

pip3 install django-haystack
pip3 install elasticsearch==2.4.1

  4、在settings.py文件中加入如下配置

INSTALLED_APPS = [
    'haystack',
]

# Haystack
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://192.168.216.137:9200/',  # 此處為elasticsearch運行的服務器ip地址,端口號默認為9200
        'INDEX_NAME': 'site',  # 指定elasticsearch建立的索引庫的名稱
    },
}

# 設置每頁顯示的數據量
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
# 當數據庫改變時,會自動更新索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

  5、后端功能的實現

# 在apps/news/search_indexes.py中創建如下類:(名稱固定為search_indexes.py)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
-------------------------------------------------

-------------------------------------------------
"""

from haystack import indexes
# from haystack import site

from .models import News


class NewsIndex(indexes.SearchIndex, indexes.Indexable):
    """
    News索引數據模型類
    可以借用 hay_stack 借助 ES 來查詢
    """
    #  主要進行關鍵字查詢
    text = indexes.CharField(document=True, use_template=True)
    id = indexes.IntegerField(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 News

    def index_queryset(self, using=None):
        """返回要建立索引的數據查詢集
        """

        return self.get_model().objects.filter(is_delete=False)

 

# 創建templates/search/indexes/news/news_text.txt文件(文件名為:模型_text.txt)
# 此模板指明當將關鍵詞通過text參數名傳遞時,可以通過news 的title、digest、content 來進行關鍵字索引查詢

{{ object.title }}
{{ object.digest }}
{{ object.content }}

 

# 在虛擬機中執行如下命令,生成索引

python manage.py rebuild_index


// es建立索引:
curl -XPUT 'http://47.98.147.102:9200/索引名稱'

// 查詢當個索引內容:
curl -XGET 'http://47.98.147.102:9200/dj31_db2/_search?pretty=true'

// 查詢所有索引
curl -XGET 'http://47.98.147.102:9200/_cat/indices?v'

 

# 后台業務邏輯實現
from haystack.views import SearchView


class Search(SearchView):
  # 必須要指定模板,用戶覆蓋默認的search/search.html template
= 'news/search.html' def create_response(self): # 接收前台用戶輸入的查詢值 # kw='python' query = self.request.GET.get('q','') if not query: show = True host_news = models.HotNews.objects.select_related('news').only('news_id','news__title','news__image_url').filter(is_delete=False).order_by('priority') paginator = Paginator(host_news,5) try: page = paginator.page(int(self.request.GET.get('page',1))) # 假如傳的不是整數 except PageNotAnInteger: # 默認返回第一頁 page = paginator.page(1) except EmptyPage: page = paginator.page(paginator.num_pages) return render(self.request,self.template,locals()) else: show=False return super().create_response()

 

# 路由
# 在apps/news/urls.py中

urlpatterns = [
    path('search/', views.Search(), name='search'),

]

 

# 自定義分頁過濾器
# 在news app下面新建 templatetags / news_template.py

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
{% extends 'base/base.html' %}
{% block title %}搜索{% endblock %}
{% load news_template %}
{% block link %}
    <link rel="stylesheet" href="../../static/css/news/search.css">

{% endblock %}

{% block main_contain %}
      <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 not show %}
                       <!-- search-list start -->
{#                        {% if not show_all %}#}
                          <div class="search-result-list">
                            <h2 class="search-result-title">
                              搜索結果 <span style="font-weight: 700;color: #ff6620;">{{ paginator.num_pages }}</span></h2>
                            <ul class="news-list">
                              {# 導入自帶高亮功能 #}
                  {# haystack自帶highlight標簽,將搜索關鍵字標為高亮 #} {% load highlight %} {% for one_news in page.object_list %}
<li class="news-item clearfix"> <a href="{% url 'news:news_detail' one_news.id %}" class="news-thumbnail" target="_blank"> <img src="{{ one_news.object.image_url }}"> </a> <div class="news-content"> <h4 class="news-title"> <a href="{% url 'news:news_detail' one_news.id %}">
                        {# 必須使用{% model_name.field_name with query %}#}
{% highlight one_news.title with query %} </a> </h4> <p class="news-details">{{ one_news.digest }}</p> <div class="news-other"> <span class="news-type">{{ one_news.object.tag.name }}</span> <span class="news-time">{{ one_news.object.update_time }}</span> <span class="news-author">{% highlight one_news.object.author.username with query %} </span> </div> </div> </li> {% endfor %} </ul> </div> {% else %} <div class="news-contain"> <div class="hot-recommend-list"> <h2 class="hot-recommend-title">熱門推薦</h2> <ul class="news-list"> {% for one_hotnews in page.object_list %} <li class="news-item clearfix"> <a href="#" class="news-thumbnail"> <img src="{{ one_hotnews.news.image_url }}"> </a> <div class="news-content"> <h4 class="news-title"> <a href="{% url 'news:news_detail' one_hotnews.news.id %}">{{ one_hotnews.news.title }}</a> </h4> <p class="news-details">{{ one_hotnews.news.digest }}</p> <div class="news-other"> <span class="news-type">{{ one_hotnews.news.tag.name }}</span> <span class="news-time">{{ one_hotnews.update_time }}</span> <span class="news-author">{{ one_hotnews.news.author.username }}</span> </div> </div> </li> {% endfor %} </ul> </div> </div> {% endif %} <!-- search-list end --> <!-- news-contain start --> {# 分頁導航 #} <div class="page-box" id="pages"> <div class="pagebar" id="pageBar"> <a class="a1">{{ page.paginator.count | default:0 }}條</a> {# 上一頁的URL地址#} {% if page.has_previous %} {% if query %} <a href="{% url 'news:search' %}?q={{ query }}&amp;page={{ page.previous_page_number }}&q={{ query }}" class="prev">上一頁</a> {% else %} <a href="{% url 'news:search' %}?page={{ page.previous_page_number }}" class="prev">上一頁</a> {% endif %} {% endif %} {# 列出所有的URL地址 頁碼#} {% 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: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:search' %}?page={{ n }}">{{ n }}</a> {% endif %} {% endif %} {% endif %} {% endfor %} {% endif %} {# next_page 下一頁的URL地址#} {% if page.has_next %} {% if query %} <a href="{% url 'news:search' %}?q={{ query }}&amp;page={{ page.next_page_number }}&q={{ query }}" class="next">下一頁</a> {% else %} <a href="{% url 'news:search' %}?page={{ page.next_page_number }}" class="next">下一頁</a> {% endif %} {% endif %} </div> </div> <!-- news-contain end --> </div> <!-- content end --> </div> {% endblock %} {% block script %} {% endblock %}

 

/* 在static/css/news/search.css中加入如下代碼: */

/* === 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
}
/* === current index end === */

五、參考

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM