1.背景
當使用者在搜尋框輸入關鍵字後,我們要為使用者提供相關的搜尋結果。可以選擇使用模糊查詢like
關鍵字實現,但是 like 關鍵字的效率極低。查詢需要在多個欄位中進行,使用 like 關鍵字也不方便,另外分詞的效果也不理想。
全文檢索方案
全文檢索即在指定的任意欄位中進行檢索查詢。
全文檢索方案需要配合搜尋引擎來實現。
搜尋引擎原理
搜尋引擎進行全文檢索時,會對資料庫中的資料進行一遍預處理,單獨建立起一份索引結構資料。
索引結構資料類似字典的索引檢索頁,裡面包含了關鍵詞與詞條的對應關係,並記錄詞條的位置。
搜尋引擎進行全文檢索時,將關鍵字在索引資料中進行快速對比查詢,進而找到資料的真實儲存位置。
2.Elasticsearch介紹
實現全文檢索的搜尋引擎,首選的是Elasticsearch
。
Elasticsearch 是用 Java 實現的,開源的搜尋引擎。
它可以快速地儲存、搜尋和分析海量資料。維基百科、Stack Overflow、Github等都採用它。
Elasticsearch
的底層是開源庫 Lucene。但是,沒法直接使用 Lucene,必須自己寫程式碼去呼叫它的介面。
分詞說明
搜尋引擎在對資料構建索引時,需要進行分詞處理。
分詞是指將一句話拆解成多個單字 或 詞,這些字或詞便是這句話的關鍵詞。
Elasticsearch
不支援對中文進行分詞建立索引,需要配合擴充套件elasticsearch-analysis-ik
來實現中文分詞處理。
3.整合Elasticsearch
3.1. Haystack介紹和安裝配置
Haystack 是在Django中對接搜尋引擎的框架,搭建了使用者和搜尋引擎之間的溝通橋樑。
我們在Django中可以通過使用 Haystack 來呼叫
Elasticsearch
搜尋引擎。
Haystack 可以在不修改程式碼的情況下使用不同的搜尋後端(比如
Elasticsearch
、Whoosh
、Solr
等等)。
Haystack安裝
$ pip install django-haystack
$ pip install elasticsearch==2.4.1複製程式碼
Haystack註冊應用和路由
在django
的配置檔案中註冊。
INSTALLED_APPS = [ 'haystack', # 全文檢索註冊]複製程式碼
在總路由中新建haystack
的路由。
urlpatterns = [url(r'^search/', include('haystack.urls')),]複製程式碼
Haystack配置
在配置檔案中配置Haystack為搜尋引擎後端
# Haystack
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://192.168.103.158:9200/', # Elasticsearch伺服器ip地址,埠號固定為9200
'INDEX_NAME': 'serach_mall', # Elasticsearch建立的索引庫的名稱
},
}
# 當新增、修改、刪除資料時,自動生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 搜尋的每頁大小
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 3複製程式碼
HAYSTACK_SIGNAL_PROCESSOR 配置項保證了在Django執行起來後,有新的資料產生時,Haystack仍然可以讓Elasticsearch實時生成新資料的索引。
3.2 Haystack建立資料索引
1.建立索引類
通過建立索引類,來指明讓搜尋引擎對哪些欄位建立索引,也就是可以通過哪些欄位的關鍵字來檢索資料。
本專案中對模型類SKU資訊進行全文檢索,所以在該模型類的應用(goods)中新建
search_indexes.py
檔案,用於存放索引類。索引類必須繼承haystack.indexes.SearchIndex
與haystack.indexes.Indexable
.
from haystack import indexes
from .models import SKU
class SKUIndex(indexes.SearchIndex, indexes.Indexable):
"""SKU索引資料模型類"""
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
"""返回建立索引的模型類"""
return SKU
def index_queryset(self, using=None):
"""返回要建立索引的資料查詢集"""
return self.get_model().objects.filter(is_launched=True) 複製程式碼
索引類
SKUIndex
說明:在
SKUIndex
建立的欄位,都可以藉助Haystack
由Elasticsearch
搜尋引擎查詢。其中
text
欄位我們宣告為document=True
,表名該欄位是主要進行關鍵字查詢的欄位。text
欄位的索引值可以由多個資料庫模型類欄位組成,具體由哪些模型類欄位組成,我們用use_template=True
表示後續通過模板來指明。
2.建立text欄位索引值模板檔案
在專案
templates
目錄中建立text欄位
使用的模板檔案具體在
templates/search/indexes/goods/sku_text.txt
檔案中定義,其中goods
為應用名,sku_text.txt
中的sku
為模型類小寫。
{{ object.id }}
{{ object.name }}
{{ object.caption }}複製程式碼
模板檔案說明:當將關鍵詞通過text引數名傳遞時
此模板指明SKU的
id
、name
、caption
作為text
欄位的索引值來進行關鍵字索引查詢。
3.手動生成初始索引
$ python manage.py rebuild_index複製程式碼
第一次需要生成索引需要執行上述命令,後續會自動生成索引。
3.3 全文檢索測試
準備測試表單
請求方法:
GET
請求地址:
/search/
請求引數:
q
<div class="search_wrap fl">
<form method="get" action="/search/" class="search_con">
<input type="text" class="input_text fl" name="q" placeholder="搜尋商品">
<input type="submit" class="input_btn fr" name="" value="搜尋">
</form>
...
...
</div>複製程式碼
然後在templates/search/
目錄下新建search.html
接收和渲染全文檢索的結果.
3.4 渲染搜尋結果
Haystack返回的資料包括:
query
:搜尋關鍵字paginator
:分頁paginator物件page
:當前頁的page物件(遍歷page
中的物件,可以得到result
物件)result.objects
: 當前遍歷出來的SKU物件。
<div class="main_wrap clearfix">
<div class=" clearfix">
<ul class="goods_type_list clearfix">
{% for result in page %}
<li>
{# object取得才是sku物件 #}
<a href="/detail/{{ result.object.id }}/"><img src="{{ result.object.default_image.url }}"></a>
<h4><a href="/detail/{{ result.object.id }}/">{{ result.object.name }}</a></h4>
<div class="operate">
<span class="price">¥{{ result.object.price }}</span>
<span>{{ result.object.comments }}評價</span>
</div>
</li>
{% else %}
<p>沒有找到您要查詢的商品。</p>
{% endfor %}
</ul>
<div class="pagenation">
<div id="pagination" class="page"></div>
</div>
</div>
</div>複製程式碼
這裡Elasticsearch替我們把django中的檢視函式寫了。
搜尋頁分頁器
<div class="main_wrap clearfix">
<div class=" clearfix">
......
<div class="pagenation">
<div id="pagination" class="page"></div>
</div>
</div>
</div>
<script type="text/javascript">
$(function () {
$('#pagination').pagination({
currentPage: {{ page.number }},
totalPage: {{ paginator.num_pages }},
callback:function (current) {
window.location.href = '/search/?q={{ query }}&page=' + current;
}
})
});
</script>複製程式碼
這裡使用的jquery.pagination.js
接收要渲染的資料,當然也可以使用其他框架的分頁器或自定義的來接收。