Django Haystack 全文檢索與關鍵詞高亮

hellogithub 發佈 2020-02-14T08:11:19+00:00

需要注意的是,目前elasticsearch 有 2 系列和 5 系列兩大版本,本來新項目的原則是儘可能採用新版本,但目前 django-haystack 在 pypi 上發布的穩定版只支持 elasticsearch2,master 分支下支持 elasticsearch5,因

作者:HelloGitHub-追夢人物

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

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

在此之前我們使用了 Django 內置的一些方法實現了一個簡單的搜索功能。但這個搜索功能實在過於簡單,沒有多大的實用性。對於一個搜尋引擎來說,至少應該能夠根據用戶的搜索關鍵詞對搜索結果進行排序以及高亮關鍵字。現在我們就來使用 django-haystack 實現這些特性。

Django Haystack 簡介

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

安裝必要依賴

安裝 django-haystack

django-haystack 安裝非常簡單,只需要執行 pipenv install django-haystack 即可。需要注意的是,目前 elasticsearch 有 2 系列和 5 系列兩大版本,本來新項目的原則是儘可能採用新版本,但目前 django-haystack 在 pypi 上發布的穩定版只支持 elasticsearch2,master 分支下支持 elasticsearch5,因此處於穩定性考慮,我們暫時使用 elasticsearch2,後續如果 django-haystack 發布了支持 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

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

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

這個鏡像從 elasticsearch 的官方基礎鏡像 2.4.6 版本進行構建,接著我們把 ik 分詞插件複製到 elasticsearch 安裝插件的目錄下,然後解壓啟用。

接著我們又把 elasticsearch.yml 配置文件複製到容器內,然後切換用戶為 elasticsearch,因為我們將以 elasticsearch 用戶和組運行 elasticsearch 服務。

elasticsearch.yml 配置文件內容很簡單:

bootstrap.memory_lock: true
network.host: 0.0.0.0

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

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

主要是加入了 elasticsearch 服務,其中 environment 和 ulimits 的參數與 elasticksearch 服務調優有關,對於簡單的博客搜索來說,調優的意義不是很大,因此這裡不做過多介紹,感興趣的可以參考 elasticksearch 的官方文檔

配置 Haystack

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

首先是把 django haystack 加入到 INSTALLED_APPS 設置里:


然後加入如下配置項:

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

HAYSTACK_SEARCH_RESULTS_PER_PAGE 指定如何對搜索結果分頁,這裡設置為每 10 項結果為一頁。

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

由於開發環境和線上環境,elasticsearch 服務的 url 地址是不同的,所以我們在 common 的配置中沒有指定 url,在 local.py 設置文件指定之:

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

處理數據

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

這是 django haystack 的規定。要相對某個 app 下的數據進行全文檢索,就要在該 app 下創建一個 search_indexes.py 文件,然後創建一個 XXIndex 類(XX 為含有被檢索數據的模型,如這裡的 Post),並且繼承 SearchIndex 和 Indexable。

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

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

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

這個數據模板的作用是對 Post.title、Post.body 這兩個欄位建立索引,當檢索的時候會對這兩個欄位做全文檢索匹配,然後將匹配的結果排序後作為搜索結果返回。

配置 URL

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

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

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

修改搜索表單

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


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

創建搜索結果頁面

haystack_search 視圖函數會將搜索結果傳遞給模板 search/search.html,因此創建這個模板文件,對搜索結果進行渲染。


高亮關鍵詞

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

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

建立索引文件

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

$ docker exec -it hellodjango_blog_tutorial_local python manage.py rebuild_index

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

修改搜尋引擎為中文分詞

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

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

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

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

防止標題被截斷

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

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

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

HAYSTACK_CUSTOM_HIGHLIGHTER = 'blog.utils.Highlighter'

在來看一下搜索效果吧!

django-haystack 中文搜索結果

線上發布

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


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

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

這樣就可以直接發布線上了!

『講解開源項目系列』——讓對開源項目感興趣的人不再畏懼、讓開源項目的發起者不再孤單。跟著我們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎留言聯繫我們、加入我們,讓更多人愛上開源、貢獻開源~

關鍵字: