站內搜尋,可以認為是針對一個網站特性內容的搜尋功能。由於內容、格式可控,站內搜尋比全網搜尋的實現要簡單很多。
簡書這個網站本身自帶一個搜尋,但是缺乏針對個人文章的搜尋,所以本文的實戰內容是解決這個痛點。
程式碼在 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/9c2fdb9fa5d1
,9c2fdb9fa5d1
是文章 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. 最簡單的搜尋引擎實現
對於每個搜尋詞檢視每個文章的標題和正文中有無該詞:
- 標題中有該搜尋詞,為該文章加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 基本原理:
- 對搜尋內容進行分詞,得到若干搜尋詞。
- 通過倒排索引找到含有搜尋詞的文章集合。
- 通過TF-IDF、餘弦相似性計算文章集合中每個文章和搜尋內容的相似性。
- 根據相似性進行排序,得到搜尋結果。
3.1 環境搭建
我們先搭建環境:
- 安裝Java。
- 官網下載最新的 6.0.0 版本,解壓。
- 安裝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/
即可。體驗效果:
4. 關於 Elasticsearch 的一些思考
4.1 如何看待停止詞?
停止詞是非常常見的單詞,例如的
、the
等。一般用法是在分詞後去掉停止詞,然後進行索引。這種做法的常見理由是減少索引大小。同時,從理論上看,也可以提升檢索速度。
相應的,這裡有兩個問題需要探討:
- 索引大小的減小量是什麼數量級,如果只是減少了1%,這種優化並無必要。
- 檢索速度的提升是什麼數量級,如果只是提升1%,說服力並不大。
是否能達到業務的需求才是目標。如果需要在搜尋的
這個詞的時候有結果,那麼上面的做法就是不合理的。
我更傾向於底層索引不啟用停止詞,而是根據業務需求在業務層進行必要的停止詞處理。
4.2 防止深度搜尋
要Elasticsearch返回搜尋結果的第10001條到第10010條資料,是一個耗時的操作,因為Elasticsearch要先得到打分最高的前10010條資料,然後從中取出第10001條到第10010條資料。
使用者感知到的搜尋介面是分頁的,每頁是固定數量的資料(如10條),使用者會跳轉到第1001頁的搜尋結果嗎?不會。第1001頁的搜尋結果有意義嗎?沒有意義,使用者應該調整搜尋詞。
綜上,應限制使用者得到的搜尋結果數量。
4.3 處理海量資料
本文的示例的資料由一個使用者的所有文章組成,資料量很小。如果簡書全站搜尋也是用Elasticsearch,它能處理好嗎?
事實上,Elasticsearch 支援分片和分散式部署,能近實時的處理海量資料。注意,索引耗時會很大,但是搜尋很快。
4.4 如何在搜尋結果中加入推廣內容
推廣內容本身也可以被Elasticsearch索引起來,根據情況插入搜尋結果中就行了。
本文釋出於樂天的開發筆記-掘金,同時釋出於樂天筆記。