欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Django Haystack 全文檢索與關(guān)鍵詞高亮的實現(xiàn)

 更新時間:2020年02月17日 09:04:20   作者:削微寒  
這篇文章主要介紹了Django Haystack 全文檢索與關(guān)鍵詞高亮的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

作者:HelloGitHub-追夢人物

文中所涉及的示例代碼,已同步更新到HelloGitHub-Team 倉庫

博客提供 RSS 訂閱應該是標配,這樣讀者就可以通過一些聚合閱讀工具訂閱你的博客,時時查看是否有文章更新,而不必每次都跳轉(zhuǎn)到博客上來查看。現(xiàn)在我們就來為博客添加 RSS 訂閱功能。

在此之前我們使用了 Django 內(nèi)置的一些方法實現(xiàn)了一個簡單的搜索功能。但這個搜索功能實在過于簡單,沒有多大的實用性。對于一個搜索引擎來說,至少應該能夠根據(jù)用戶的搜索關(guān)鍵詞對搜索結(jié)果進行排序以及高亮關(guān)鍵字?,F(xiàn)在我們就來使用 django-haystack 實現(xiàn)這些特性。

Django Haystack 簡介

django-haystack 是一個專門提供搜索功能的 django 第三方應用,它支持 Solr、Elasticsearch、Whoosh、Xapian 等多種搜索引擎,上一版本的教程中我們使用 Whoosh 加 jieba 中文分詞的方案,原因是為了簡單,無需安裝外部服務。但現(xiàn)在有了 docker,安裝一個外部服務就是輕而易舉的事情,所以這次我們采用更為強大的 elasticsearch 作為我們博客的搜索引擎,同時使用 elasticsearch 的中文分詞插件 ik,來提升中文搜索的效果。

安裝必要依賴

安裝 django-haystack

django-haystack 安裝非常簡單,只需要執(zhí)行 pipenv install django-haystack 即可。需要注意的是,目前 elasticsearch 有 2 系列和 5 系列兩大版本,本來新項目的原則是盡可能采用新版本,但目前 django-haystack 在 pypi 上發(fā)布的穩(wěn)定版只支持 elasticsearch2,master 分支下支持 elasticsearch5,因此處于穩(wěn)定性考慮,我們暫時使用 elasticsearch2,后續(xù)如果 django-haystack 發(fā)布了支持 elasticsearch5 的pypi版本,我們會升級到 elasticsearch5,有了 docker,升級就是輕而易舉的事情。

由于使用 elasticsearch 服務,haystack 連接 elasticsearch 需要 python 版本的 SDK 支持,因此還需要安裝 elasticsearch python SDK,這里我們不要直接使用 pipenv 安裝,而是手動編輯 Pipfile 文件,指定 SDK 的版本,否則 pipenv 默認會安裝最新版。打開 Pipfile 文件,將依賴手動添加到 packages 板塊下:

[packages]
django = "~=2.2"
elasticsearch = ">=2,<3"

安裝 elasticsearch 2

接下來就是構(gòu)建一個新的容器來運行 elasticsearch 服務,因此首先需要來編排容器鏡像,回顧一下容器鏡像的目錄結(jié)構(gòu):

compose\
 local\
 production\
  django\
  nginx\

由于 elasticsearch 在線上環(huán)境和本地測試都要使用,我們把鏡像編排在 production 目錄下,新建一個 elasticsearch 目錄,用來存放和 elasticsearch 相關(guān)的內(nèi)容。Dockfile 內(nèi)容如下:

FROM elasticsearch:2.4.6-alpine

COPY ./compose/production/elasticsearch/elasticsearch-analysis-ik-1.10.6.zip /usr/share/elasticsearch/plugins/
RUN cd /usr/share/elasticsearch/plugins/ && mkdir ik && unzip elasticsearch-analysis-ik-1.10.6.zip -d ik/
RUN rm /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-1.10.6.zip

USER root
COPY ./compose/production/elasticsearch/elasticsearch.yml /usr/share/elasticsearch/config/
RUN chown elasticsearch:elasticsearch /usr/share/elasticsearch/config/elasticsearch.yml

USER elasticsearch

這個鏡像從 elasticsearch 的官方基礎(chǔ)鏡像 2.4.6 版本進行構(gòu)建,接著我們把 ik 分詞插件復制到 elasticsearch 安裝插件的目錄下,然后解壓啟用。

接著我們又把 elasticsearch.yml 配置文件復制到容器內(nèi),然后切換用戶為 elasticsearch,因為我們將以 elasticsearch 用戶和組運行 elasticsearch 服務。

elasticsearch.yml 配置文件內(nèi)容很簡單:

bootstrap.memory_lock: true
network.host: 0.0.0.0

其中 bootstrap.memory_lock 這個參數(shù)是為了提高 elasticsearch 的效率(涉及到 JVM 相關(guān)的優(yōu)化,不做過多介紹)。network.host 指定服務啟動的地址。

接著修改 docker compose 文件,我們先在本地啟動,因此修改 local.yml 文件,加入 elasticsearch 服務:

version: '3'

volumes:
 database_local:
 esdata_local:

services:
 hellodjango_blog_tutorial_local:
 # 其它配置不變...
 depends_on:
  - elasticsearch_local

 elasticsearch_local:
 build:
  context: .
  dockerfile: ./compose/production/elasticsearch/Dockerfile
 image: elasticsearch_local
 container_name: elasticsearch_local
 volumes:
  - esdata_local:/usr/share/elasticsearch/data
 ports:
  - "9200:9200"
 environment:
  - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
 ulimits:
  memlock:
  soft: -1
  hard: -1
  nproc: 65536
  nofile:
  soft: 65536
  hard: 65536

主要是加入了 elasticsearch 服務,其中 environment 和 ulimits 的參數(shù)與 elasticksearch 服務調(diào)優(yōu)有關(guān),對于簡單的博客搜索來說,調(diào)優(yōu)的意義不是很大,因此這里不做過多介紹,感興趣的可以參考 elasticksearch 的官方文檔。

配置 Haystack

安裝好 django haystack 后需要在項目的 settings.py 做一些簡單的配置。

首先是把 django haystack 加入到 INSTALLED_APPS 設(shè)置里:

blogproject/settings.py

INSTALLED_APPS = [
 'django.contrib.admin',
 # 其它 app...
 'haystack',
 'blog',
 'comments',
]

然后加入如下配置項:

blogproject/common.py

# 搜索設(shè)置
HAYSTACK_CONNECTIONS = {
 'default': {
  'ENGINE': 'haystack.elasticsearch2_backend.Elasticsearch2SearchEngine',
  'URL': '',
  'INDEX_NAME': 'hellodjango_blog_tutorial',
 },
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

HAYSTACK_CONNECTIONS 的 ENGINE 指定了 django haystack 使用的搜索引擎,這里我們使用了 haystack 默認的 Elasticsearch2 搜索引擎。PATH 指定了索引文件需要存放的位置,我們設(shè)置為項目根目錄 BASE_DIR 下的 whoosh_index 文件夾(在建立索引是會自動創(chuàng)建)。

HAYSTACK_SEARCH_RESULTS_PER_PAGE 指定如何對搜索結(jié)果分頁,這里設(shè)置為每 10 項結(jié)果為一頁。

HAYSTACK_SIGNAL_PROCESSOR 指定什么時候更新索引,這里我們使用 haystack.signals.RealtimeSignalProcessor,作用是每當有文章更新時就更新索引。由于博客文章更新不會太頻繁,因此實時更新沒有問題。

由于開發(fā)環(huán)境和線上環(huán)境,elasticsearch 服務的 url 地址是不同的,所以我們在 common 的配置中沒有指定 url,在 local.py 設(shè)置文件指定之:

HAYSTACK_CONNECTIONS['default']['URL'] = 'http://elasticsearch_local:9200/'

處理數(shù)據(jù)

接下來就要告訴 django haystack 使用哪些數(shù)據(jù)建立索引以及如何存放索引。如果要對 blog 應用下的數(shù)據(jù)進行全文檢索,做法是在 blog 應用下建立一個 search_indexes.py 文件,寫上如下代碼:

blog/search_indexes.py

from haystack import indexes
from .models import Post


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

 def get_model(self):
  return Post

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

這是 django haystack 的規(guī)定。要相對某個 app 下的數(shù)據(jù)進行全文檢索,就要在該 app 下創(chuàng)建一個 search_indexes.py 文件,然后創(chuàng)建一個 XXIndex 類(XX 為含有被檢索數(shù)據(jù)的模型,如這里的 Post),并且繼承 SearchIndex 和 Indexable。

為什么要創(chuàng)建索引?索引就像是一本書的目錄,可以為讀者提供更快速的導航與查找。在這里也是同樣的道理,當數(shù)據(jù)量非常大的時候,若要從這些數(shù)據(jù)里找出所有的滿足搜索條件的幾乎是不太可能的,將會給服務器帶來極大的負擔。所以我們需要為指定的數(shù)據(jù)添加一個索引(目錄),在這里是為 Post 創(chuàng)建一個索引,索引的實現(xiàn)細節(jié)是我們不需要關(guān)心的,我們只關(guān)心為哪些字段創(chuàng)建索引,如何指定。

每個索引里面必須有且只能有一個字段為 document=True,這代表 django haystack 和搜索引擎將使用此字段的內(nèi)容作為索引進行檢索(primary field)。注意,如果使用一個字段設(shè)置了document=True,則一般約定此字段名為text,這是在 SearchIndex 類里面一貫的命名,以防止后臺混亂,當然名字你也可以隨便改,不過不建議改。

并且,haystack 提供了 use_template=True 在 text 字段中,這樣就允許我們使用數(shù)據(jù)模板去建立搜索引擎索引的文件,說得通俗點就是索引里面需要存放一些什么東西,例如 Post 的 title 字段,這樣我們可以通過 title 內(nèi)容來檢索 Post 數(shù)據(jù)了。舉個例子,假如你搜索 Python ,那么就可以檢索出 title 中含有 Python 的Post了,怎么樣是不是很簡單?數(shù)據(jù)模板的路徑為 templates/search/indexes/youapp/<model_name>_text.txt(例如 templates/search/indexes/blog/post_text.txt),其內(nèi)容為:

templates/search/indexes/blog/post_text.txt

{{ object.title }}
{{ object.body }}

這個數(shù)據(jù)模板的作用是對 Post.title、Post.body 這兩個字段建立索引,當檢索的時候會對這兩個字段做全文檢索匹配,然后將匹配的結(jié)果排序后作為搜索結(jié)果返回。

配置 URL

接下來就是配置 URL,搜索的視圖函數(shù)和 URL 模式 django haystack 都已經(jīng)幫我們寫好了,只需要項目的 urls.py 中包含它:

blogproject/urls.py

urlpatterns = [
 # 其它...
 path('search/', include('haystack.urls')),
]

另外在此之前我們也為自己寫的搜索視圖配置了 URL,把那個 URL 刪掉,以免沖突:

blog/urls.py

# path('search/', views.search, name='search'),

修改搜索表單

修改一下搜索表單,讓它提交數(shù)據(jù)到 django haystack 搜索視圖對應的 URL:

<form role="search" method="get" id="searchform" action="{% url 'haystack_search' %}">
 <input type="search" name="q" placeholder="搜索" required>
 <button type="submit"><span class="ion-ios-search-strong"></span></button>
</form>

主要是把表單的 action 屬性改為 {% url 'haystack_search' %}

創(chuàng)建搜索結(jié)果頁面

haystack_search 視圖函數(shù)會將搜索結(jié)果傳遞給模板 search/search.html,因此創(chuàng)建這個模板文件,對搜索結(jié)果進行渲染:

templates/search/search.html

{% extends 'base.html' %}
{% load highlight %}

{% block main %}
 {% if query %}
 {% for result in page.object_list %}
  <article class="post post-{{ result.object.pk }}">
  <header class="entry-header">
   <h1 class="entry-title">
   <a href="{{ result.object.get_absolute_url }}" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{% highlight result.object.title with query %}</a>
   </h1>
   <div class="entry-meta">
     <span class="post-category">
      <a href="{% url 'blog:category' result.object.category.pk %}" rel="external nofollow" >
       {{ result.object.category.name }}</a></span>
   <span class="post-date"><a href="#" rel="external nofollow" rel="external nofollow" >
       <time class="entry-date" datetime="{{ result.object.created_time }}">
        {{ result.object.created_time }}</time></a></span>
   <span class="post-author"><a href="#" rel="external nofollow" rel="external nofollow" >{{ result.object.author }}</a></span>
   <span class="comments-link">
      <a href="{{ result.object.get_absolute_url }}#comment-area" rel="external nofollow" >
       {{ result.object.comment_set.count }} 評論</a></span>
   <span class="views-count"><a
     href="{{ result.object.get_absolute_url }}" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ result.object.views }} 閱讀</a></span>
   </div>
  </header>
  <div class="entry-content clearfix">
   <p>{% highlight result.object.body with query %}</p>
   <div class="read-more cl-effect-14">
   <a href="{{ result.object.get_absolute_url }}" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="more-link">繼續(xù)閱讀 <span
     class="meta-nav">→</span></a>
   </div>
  </div>
  </article>
 {% empty %}
  <div class="no-post">沒有搜索到你想要的結(jié)果!</div>
 {% endfor %}

 {% if page.has_previous or page.has_next %}
  <div class="text-center" style="margin-top: 30px">
  {% if page.has_previous %}
   <a href="?q={{ query }}&amp;page={{ page.previous_page_number }}" rel="external nofollow" >{% endif %}&laquo; Previous
  {% if page.has_previous %}</a>{% endif %}
  <span style="margin: 0 10px">|</span>
  {% if page.has_next %}<a href="?q={{ query }}&amp;page={{ page.next_page_number }}" rel="external nofollow" >{% endif %}Next
  &raquo;{% if page.has_next %}</a>{% endif %}
  </div>
 {% endif %}
 {% else %}
 請輸入搜索關(guān)鍵詞,例如 django
 {% endif %}
{% endblock main %}

這個模板基本和 blog/index.html 一樣,只是由于 haystack 對搜索結(jié)果做了分頁,傳給模板的變量是一個 page 對象,所以我們從 page 中取出這一頁對應的搜索結(jié)果,然后對其循環(huán)顯示,即 {% for result in page.object_list %}。另外要取得 Post(文章)以顯示文章的數(shù)據(jù)如標題、正文,需要從 result 的 object 屬性中獲取。query 變量的值即為用戶搜索的關(guān)鍵詞。

高亮關(guān)鍵詞

注意到百度的搜索結(jié)果頁面,含有用戶搜索的關(guān)鍵詞的地方都是被標紅的,在 django haystack 中實現(xiàn)這個效果也非常簡單,只需要使用 {% highlight %} 模板標簽即可,其用法如下:

# 使用默認值 
{% highlight result.summary with query %} 
 
# 這里我們?yōu)?{{ result.summary }} 里所有的 {{ query }} 指定了一個<div></div>標簽,并且將class設(shè)置為highlight_me_please,這樣就可以自己通過CSS為{{ query }}添加高亮效果了,怎么樣,是不是很科學呢 
{% highlight result.summary with query html_tag "div" css_class "highlight_me_please" %} 
 
# 可以 max_length 限制最終{{ result.summary }} 被高亮處理后的長度
{% highlight result.summary with query max_length 40 %} 

在博客文章搜索頁中我們對 title 和 body 做了高亮處理:{% highlight result.object.title with query %},{% highlight result.object.body with query %}。高亮處理的原理其實就是給文本中的關(guān)鍵字包上一個 span 標簽并且為其添加 highlighted 樣式(當然你也可以修改這個默認行為,具體參見上邊給出的用法)。因此我們還要給 highlighted 類指定樣式,在 base.html 中添加即可:

base.html

<head>
 <title>Black &amp; White</title>
 ...
 <style>
  /* 搜索關(guān)鍵詞高亮 */
  span.highlighted {
   color: red;
  }
 </style>
 ...
</head>

建立索引文件

最后一步就是建立索引文件了,運行命令 :

$ docker exec -it hellodjango_blog_tutorial_local python manage.py rebuild_index

就可以建立索引文件了。一切就緒后,就可以嘗試搜索了。但是體驗下來會發(fā)現(xiàn)搜索的結(jié)果并不是很友好,很多關(guān)鍵詞文章中命名存在但搜索結(jié)果中卻沒有顯示,原因是 haystack 專門為英文搜索設(shè)計,如果使用其默認的搜索引擎分詞器,中文搜索的結(jié)果就不是很理想,接下來我們來將它默認的分詞器設(shè)置為中文分詞器。

修改搜索引擎為中文分詞

還記得文章開頭編排 elasticsearch 的 Docker 鏡像時,我們將一個 elasticsearch 的中文分詞插件復制到了 elasticsearch 的插件目錄,接下來要做的,就是讓 haystack 在創(chuàng)建索引時,使用指定的插件來對進行分詞并創(chuàng)建索引,具體做法是,首先在 blog 應用下創(chuàng)建一個 elasticsearch2_ik_backend.py,代碼如下:

from haystack.backends.elasticsearch2_backend import Elasticsearch2SearchBackend, Elasticsearch2SearchEngine

DEFAULT_FIELD_MAPPING = {'type': 'string', "analyzer": "ik_max_word", "search_analyzer": "ik_smart"}


class Elasticsearch2IkSearchBackend(Elasticsearch2SearchBackend):

 def __init__(self, *args, **kwargs):
  self.DEFAULT_SETTINGS['settings']['analysis']['analyzer']['ik_analyzer'] = {
   "type": "custom",
   "tokenizer": "ik_max_word",
  }
  super(Elasticsearch2IkSearchBackend, self).__init__(*args, **kwargs)


class Elasticsearch2IkSearchEngine(Elasticsearch2SearchEngine):
 backend = Elasticsearch2IkSearchBackend

這些代碼的作用是,繼承 haystack 默認的 Elasticsearch2SearchBackend 和 Elasticsearch2SearchEngine,覆蓋掉它的一些默認行為,這里主要就是讓 haystack 在創(chuàng)建索引時,使用指定的 ik 分詞器。

由于自定義了搜索引擎,因此在配置文件中將原來指定的 Elasticsearch2SearchEngine 替換為自定義的 Engine:

# 搜索設(shè)置
HAYSTACK_CONNECTIONS = {
 'default': {
  'ENGINE': 'blog.elasticsearch2_ik_backend.Elasticsearch2IkSearchEngine',
  'URL': '',
  'INDEX_NAME': 'hellodjango_blog_tutorial',
 },
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

由于修改了索引創(chuàng)建方式,因此需要重建一下索引:python manage.py rebuild_index。然后就可以查看搜索結(jié)果了,中文搜索體驗是不是好了很多?

防止標題被截斷

haystack 在展示搜索結(jié)果時,默認行為是將第一個出現(xiàn)的關(guān)鍵詞前的內(nèi)容截斷,被截掉的部分用省略號代替。對于正文來說,因為內(nèi)容較多,截斷是合理的,但是對于標題這種較短的內(nèi)容來說,截斷就沒有必要了。同樣的,我們通過繼承的方式,替換掉 haystack 的默認行為。我們在 blog/utils.py 中繼承 HaystackHighlighter 這個用于高亮搜索關(guān)鍵詞的輔助類。

from django.utils.html import strip_tags
from haystack.utils import Highlighter as HaystackHighlighter


class Highlighter(HaystackHighlighter):
 """
 自定義關(guān)鍵詞高亮器,不截斷過短的文本(例如文章標題)
 """

 def highlight(self, text_block):
  self.text_block = strip_tags(text_block)
  highlight_locations = self.find_highlightable_words()
  start_offset, end_offset = self.find_window(highlight_locations)
  if len(text_block) < self.max_length:
   start_offset = 0
  return self.render_html(highlight_locations, start_offset, end_offset)

關(guān)鍵代碼是:if len(text_block) < self.max_length:,start_offset 是 haystack 根據(jù)關(guān)鍵詞算出來第一個關(guān)鍵詞在文本中出現(xiàn)的位置。max_length 指定了展示結(jié)果的最大長度。我們在代碼中做一個判斷,如果文本內(nèi)容 text_block 沒有超過允許的最大長度,就將 start_offset 設(shè)為 0,這樣就從文本的第一個字符開始展示,標題這種短文本就不會被截斷了。

然后設(shè)置,讓 haystack 在高亮文本時,使用我們自定義的輔助類:

HAYSTACK_CUSTOM_HIGHLIGHTER = 'blog.utils.Highlighter'

在來看一下搜索效果吧!

 

線上發(fā)布

以上步驟都是在本地運行調(diào)試的,elasticsearch 服務也是在本地的 Docker 容器中運行,接下來在 production.yml 中加入 elasticsearch 服務,就可以發(fā)布線上了,配置內(nèi)容和 local.yml 是一樣的,只是簡單修改一下服務名和容器名等命名:

 elasticsearch:
 build:
  context: .
  dockerfile: ./compose/production/elasticsearch/Dockerfile
 image: hellodjango_blog_tutorial_elasticsearch
 container_name: hellodjango_blog_tutorial_elasticsearch
 volumes:
  - esdata:/usr/share/elasticsearch/data
 ports:
  - "9200:9200"
 environment:
  - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
 ulimits:
  memlock:
  soft: -1
  hard: -1
  nproc: 65536
  nofile:
  soft: 65536
  hard: 65536

別忘了修改 settings/production.py,修改線上環(huán)境 elasticsearch 服務的連接地址:

HAYSTACK_CONNECTIONS['default']['URL'] = 'http://hellodjango_blog_tutorial_elasticsearch:9200/'

這樣就可以直接發(fā)布線上了!

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論