基於 Elasticsearch 的站內搜尋引擎實戰

樂天的開發筆記發表於2019-03-04

站內搜尋,可以認為是針對一個網站特性內容的搜尋功能。由於內容、格式可控,站內搜尋比全網搜尋的實現要簡單很多。

簡書這個網站本身自帶一個搜尋,但是缺乏針對個人文章的搜尋,所以本文的實戰內容是解決這個痛點。

程式碼在 github.com/letiantian/…,可以使用下面的方式把程式碼下載下來檢視:

git clone https://github.com/letiantian/jianshu-site-search.git
複製程式碼

程式碼在Python2.7下執行。需要安裝以下依賴:

pip install elasticsearch==6.0.0 --user
pip install uniout --user
pip install requests --user
pip install beautifulsoup4 --user
pip install Django --user
複製程式碼

1. 資料來源

如果是簡書給自己做個人搜尋,從資料庫裡拿就行了。

我這種情況,自然用爬蟲抓取。

1.1 抓取什麼內容?

抓取某個人所有的文章,最終是URL標題正文三個部分。

1.2 如何抓取?

www.jianshu.com/u/7fe2e7bb7…這個(隨便找的)使用者主頁為例。7fe2e7bb7d47可以認為是這個使用者的ID。 文章地址類似http://www.jianshu.com/p/9c2fdb9fa5d19c2fdb9fa5d1是文章 ID。

經過分析,可以以此請求下面的地址,從中解析出文章地址,得到地址集合:

http://www.jianshu.com/u/7fe2e7bb7d47?order_by=shared_at&page=1
http://www.jianshu.com/u/7fe2e7bb7d47?order_by=shared_at&page=2
http://www.jianshu.com/u/7fe2e7bb7d47?order_by=shared_at&page=3
// ... page的值不斷增加
// ... 當page不存在的時候,簡書會返回page=1的內容,這時候停止抓取
複製程式碼

然後,依次抓取文章內容,儲存下來。

crawler.py 用於抓取文章,使用方法:

python crawler.py 7fe2e7bb7d47
複製程式碼

文章對應的網頁會儲存到data目錄,用文章ID命名。

2. 最簡單的搜尋引擎實現

對於每個搜尋詞檢視每個文章的標題和正文中有無該詞:

  1. 標題中有該搜尋詞,為該文章加2分。
  2. 正文中有該搜尋詞,為該文章加1分。

一篇文章命中的搜尋詞越多,分值越高。

將結果排序輸出即可。

程式碼實現在simple_search.py ,使用方法:

$ python simple_search.py 人民 名義 
你輸入了: 人民 名義
搜尋結果:

url:    http://www.jianshu.com/p/6659d5fc5503
title:  《人民的名義》走紅的背後 文化產業投資難以言說的痛
score:  6

url:    http://www.jianshu.com/p/ee594ea42815
title:  LP由《人民的名義》反思 GP投資權力真空怎麼破
score:  6

url:    http://www.jianshu.com/p/4ef650769f73
title:  弘道資本:投資人人貸、ofo 人民幣基金逆襲的中國樣本
score:  3
複製程式碼

這種方法的缺點是:

  • 因為是遍歷每個文章,文章變多後,速度會變慢
  • 搜尋結果排序不理想
  • 沒有引入中文分詞特性

3. 基於 Elasticsearch 的實現

Elasticsearch 是一個通用的搜尋引擎解決方案,提供了優雅的 HTTP Restful 介面、豐富的官方文件。 阮一峰為它寫了一份簡明易懂的教程:全文搜尋引擎 Elasticsearch 入門教程,推薦閱讀。

Elasticsearch 基本原理:

  1. 對搜尋內容進行分詞,得到若干搜尋詞。
  2. 通過倒排索引找到含有搜尋詞的文章集合。
  3. 通過TF-IDF、餘弦相似性計算文章集合中每個文章和搜尋內容的相似性。
  4. 根據相似性進行排序,得到搜尋結果。

3.1 環境搭建

我們先搭建環境:

  1. 安裝Java。
  2. 官網下載最新的 6.0.0 版本,解壓。
  3. 安裝ik分詞外掛。
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.0.0/elasticsearch-analysis-ik-6.0.0.zip
複製程式碼

或者下載下來解壓到Elasticsearch的plugins目錄。 4. 啟動:

./bin/elasticsearch
複製程式碼

環境搭建完成。

3.2 建立索引

python es_create_index.py
複製程式碼

建立時指定了分詞器。

3.3 索引資料

python es_index_data.py
複製程式碼

為了防止一篇文章被重複索引,新增索引時 Document ID 設定為文章 ID。

3.4 搜尋

python es_search.py 人民的名義
複製程式碼

高亮搜尋結果:

python es_hl_search.py 人民的名義
複製程式碼

3.5 基於web的搜尋

基於Django實現了一個簡單的web介面。執行:

python webui/manage.py runserver
複製程式碼

瀏覽器訪問http://127.0.0.1:8000/即可。體驗效果:

基於 Elasticsearch 的站內搜尋引擎實戰

4. 關於 Elasticsearch 的一些思考

4.1 如何看待停止詞?

停止詞是非常常見的單詞,例如the等。一般用法是在分詞後去掉停止詞,然後進行索引。這種做法的常見理由是減少索引大小。同時,從理論上看,也可以提升檢索速度。 相應的,這裡有兩個問題需要探討:

  1. 索引大小的減小量是什麼數量級,如果只是減少了1%,這種優化並無必要。
  2. 檢索速度的提升是什麼數量級,如果只是提升1%,說服力並不大。

是否能達到業務的需求才是目標。如果需要在搜尋這個詞的時候有結果,那麼上面的做法就是不合理的。

我更傾向於底層索引不啟用停止詞,而是根據業務需求在業務層進行必要的停止詞處理。

4.2 防止深度搜尋

要Elasticsearch返回搜尋結果的第10001條到第10010條資料,是一個耗時的操作,因為Elasticsearch要先得到打分最高的前10010條資料,然後從中取出第10001條到第10010條資料。

使用者感知到的搜尋介面是分頁的,每頁是固定數量的資料(如10條),使用者會跳轉到第1001頁的搜尋結果嗎?不會。第1001頁的搜尋結果有意義嗎?沒有意義,使用者應該調整搜尋詞。

綜上,應限制使用者得到的搜尋結果數量。

4.3 處理海量資料

本文的示例的資料由一個使用者的所有文章組成,資料量很小。如果簡書全站搜尋也是用Elasticsearch,它能處理好嗎?

事實上,Elasticsearch 支援分片和分散式部署,能近實時的處理海量資料。注意,索引耗時會很大,但是搜尋很快。

4.4 如何在搜尋結果中加入推廣內容

推廣內容本身也可以被Elasticsearch索引起來,根據情況插入搜尋結果中就行了。

本文釋出於樂天的開發筆記-掘金,同時釋出於樂天筆記

相關文章