【项目介绍】搜索引擎
文章目錄
- BoostSearch / 搜索引擎
- 開發語言
- 開發環境
- 依賴庫
- 項目簡介
- 項目特點
- 項目鏈接
- 演示
 
- 項目介紹與難點分析
- 解析
- 分詞
- 索引
- 正排索引
- 倒排索引
 
- 權重
- 包裝
 
BoostSearch / 搜索引擎
開發語言
C/C++,HTML,CSS,JavaScript
開發環境
CentOS7、vim、g++、gdb、git、Makefile
依賴庫
jsoncpp, cppjieba, http-lib, boost
項目簡介
BoostSearch是一個基于boost文檔的站內搜索引擎,當用戶在頁面上輸入查詢詞后,就會快速的查詢出相關的boost在線文檔,彌補了boost在線文檔中沒有搜索的缺陷。
項目特點
- 對離線版本的html文檔進行解析,將解析結果整理為一個行本文文件。
- 讀取處理好的行文本文件進行分詞、權重計算等操作,在內存中構造出正排索引和倒排索引。
- 對查詢詞進行分詞、觸發,依據相關度對查詢結果進行排序,并以Json格式進行包裝后序列化為字符串返回。
- 通過HTTP服務器搭載搜索頁面, 為外部提供服務。
項目鏈接
github
演示
項目介紹與難點分析
具體的模塊劃分以及功能分析我以上圖的形式展現了出來,本文主要說明項目中的一些難點以及問題分析,至于具體實現以及其他內容請參考源代碼
 https://github.com/HONGYU-LEE/BoostSearch
解析
無論是站內搜索引擎還是全網搜索引擎,第一步都必須對獲取的文件進行一個預處理。
預處理分為兩個步驟
無論是官方提供的文檔文件,還是通過爬蟲在網上爬取的文件,都會夾雜著許多除了HTML外的例如圖片、音樂、測試代碼等搜索引擎不需要的東西,所以第一步就是將從這些雜亂的文件中,找出所有的HTML。
而C++標準庫中并沒有提供一個能夠遍歷文件的方法,而linux的系統函數dirent也可以做到,但是在我上一個項目MiniFTP中獲取列表那一塊可以看到,這玩意并不好用,所以這里我用的是Boost庫中的filesystem模塊。它會通過一個迭代器來遞歸找到該目錄下的所有文件,來達到遍歷的效果
找到文件后,第二步就是將文章中的關鍵信息提取出來
我們需要的信息只有三個,標題、URL、正文。
標題:標題的提取方法很簡單,只需要找到HTML中title這個標簽中的內容即可
URL:URL的處理也很簡單,通過查詢官方在線文檔,可以看到URL的構成就是官方文檔地址加上文件名,所以我們只需要提取出之前枚舉的文件的名字,將其和文檔地址進行組合,就可以獲得到URL
正文:接下來我們需要做的,就是過濾掉所有的標簽以及標簽中的內容,剩下的text內容就是我們需要的正文。并且為了之后方便處理,將所有的換行符全部替換成為空格,將每篇的正文都單獨處理成一行。
提取出信息后,就需要對信息進行劃分并保存。
我們所要做的,就是將每一篇文檔的信息全部匯總到一行中,即處理為一個行文本文件,使得每行數據就對對應著一篇文檔,這樣我們在后續處理的時候只需要按行來提取出信息,就可以達到遍歷所有數據的效果。
接著,還需要通過某種方式來劃分開標題、URL、正文。
考慮到這是一個行文本文件,換行、空格、回車等符號則直接pass,并且作為分隔符的符號必須要在文章中沒有出現過,考慮到這一點,就可以使用不可見字符(文章中不可能出現的字符)
在這里我選擇的是\1(SOH符號),通過這個符號對所有的標題、URL、正文進行劃分。
?
搜索的本質其實就是找到出現過用戶查詢內容的文章
要做到這一點其實并不復雜,可以通過比對文章內容以及用戶查詢詞來完成。
所以這時需要進行兩個步驟,一個是分詞,一個是索引。
分詞
分詞很好理解,就是將用戶輸入的字符串,分解成為一個一個的關鍵詞,然后再通過關鍵詞去進行內容的查找。
例如對“今天的天氣很好啊”這句話進行分詞
今天/的/天氣/很好/啊
對于我們人來說,由于我們有自主思考的能力,加上了出生至今的經驗以及積累,分詞對于我們來說是很簡單的一件事,但是對于計算機而言,這并不是一件簡單的事情。
例如這句出自神雕俠侶中的一句話,需要結合其中的背景來理解,機器則很難理解
“我也想過過兒過過的生活” = 我/也想/過/過兒/過/過的/生活
并且有些句子可能會解析出多種結果
“在北京大學生活區喝進口紅酒”
1.在/北京/大學/生活區/喝/進口/紅酒
2.在/北京大學/生活區/喝/進口紅酒
3.在/北京/大學生活區/喝/進口紅酒
…
并且在句子中會經常出現一些如“的”,“了”,“呢”,“嗎”,"像"等等等語氣詞或者結束詞會不可避免的大量出現,但是這些詞又沒有什么實際上的意義,所以我們將其稱為**“暫停詞/停用詞”**,不會將其放入權重的計算中。
考慮到分詞的困難巨大,甚至比我們整個項目都要復雜,所以這里就不再自己造輪子,而是引入了第三方庫結巴分詞——https://github.com/yanyiwu/cppjieba
索引
接著就到了整個項目中最核心的部分,索引。
即使本項目是一個站內的搜索引擎,只有8000多篇文檔, 但是實際算下來字符數量可能多達千萬甚至更多,如果直接進行遍歷搜索,那么速度將會慢的嚇人。而在龐大的數據中想要快速的查詢到指定的數據,那么馬上就能想到的方法就是建立索引。
索引就相當與我們看書時文章的一個目錄,我們可以通過目錄上的頁數來快速定位到需要查詢的數據,所以我們以下兩種索引,正排索引與倒排索引。
正排索引
正排索引的作用即根據文檔ID來查找文檔信息
//文章信息 struct DocInfo {int64_t doc_id; //文檔IDstring title; //文檔標題string url; //文檔URLstring content; //文檔正文 };因為我們最終需要查詢的結果只需要文檔的標題、URL、正文,所以正排索引所存儲的文檔信息就使用上面這種結構。
vector<DocInfo> forward_index; //正排索引接著,就需要建立起ID與文檔信息的映射關系,由于文檔本身并沒有規定固定的ID,所以這里我就直接使用數組下標來作為文檔的ID,將其以vector的形式進行存儲(如果規定了ID就使用unordered_map)
倒排索引
倒排索引的作用即根據文檔片段來查找相關的文檔ID
//權重信息 struct Weight {int64_t doc_id; //文檔IDint weight; //權重string word; //關鍵詞 }; typedef vector<Weight> InvertedList; //倒排拉鏈我使用上面的Weight結構來存儲每個關鍵詞所對應出現的文章與該文章的權重大小。
由于一個Weight對應著該詞的其中一篇相關文章,所以為了方便查詢,我將該詞的所有相關文章以一個vector進行存儲,構成一個倒排拉鏈的結構,并將這個倒排拉鏈作為倒排索引的節點。這樣通過關鍵詞來查找到倒排拉鏈時,就可以通過直接遍歷倒排拉鏈來獲取到所有的相關文檔。
unordered_map<string, InvertedList> invert_index; //倒排索引關鍵詞與相關文檔的映射使用哈希表(unordered_map)建立
下面介紹下索引的建立流程
假設我們此時有兩篇文章,首先進行分詞
接著,根據文檔信息構建出正排索引
文檔ID——》文檔信息
接著,依據每個關鍵詞出現過的文章,構建出倒排索引
關鍵詞——》相關文檔ID
建立了這樣一個索引體系之后,在查詢時,我們只需要對查詢詞進行分詞,再通過倒排索引查詢到對應的倒排拉鏈,權重信息,來為每個倒排拉鏈的文檔進行排序,就可以根據相關度高低以此獲取到對應的文檔ID,再通過正排索引就可以獲取到我們需要的文檔信息了。
權重
由于我們存儲的數據量不是很大,只有大概8000多個文檔,并且該項目只是用于學習搜索引擎原理,所以這里就使用線性公式來進行權重的計算,計算的依據就是關鍵詞的詞頻。
首先用以下結構來統計關鍵詞在文章的正文以及標題中分別出現的次數
struct WordCount {WordCount() : title_count(0), content_count(0){} int title_count; //標題出現頻率int content_count; //正文出現頻率 };關鍵詞在正文中和標題中出現的權重應該不一樣。
因為標題即對正文的高度概括,在標題中出現的內容往往與實際內容相關,而在正文即使出現,也有可能只是順口一提或者引用,所以我認為在標題中出現的權重應該要比在正文出現要高
所以推導的公式如下:權重 = 標題出現次數 * 10 + 正文出現次數
//權重 = 標題出現次數 * 10 + 正文出現次數 weight.weight = word_pair.second.title_count * 10 + word_pair.second.content_count;包裝
對于查詢的結果,由于我們并不需要將所有的信息都展示在搜索頁面上,只需要展示關鍵信息,所以我們需要的內容只有標題、URL、描述。
前兩個已經獲取了,但是描述還需要我們自己從正文中提取。
關于描述信息的提取,我的思路如下。
根據以上條件,我推導出以下描述信息提取公式
返回內容 = 150字節 = 關鍵詞往前50字節 + 關鍵詞(包括關鍵詞)往后100個字節
這樣,我們就可以構建出最后查詢出的結果。
但是我們最終查詢的結果并不能直接返回給調用者,還需要對其進行包裝。
這里我采用的是JSON格式,借助了第三方庫JSONCPP(https://github.com/open-source-parsers/jsoncpp)來進行包裝
JSON的格式如下
[{"title" : "標題","url" : "url","desc" : "描述"}{"title" : "標題","url" : "url","desc" : "描述"}{"title" : "標題","url" : "url","desc" : "描述"}.................. ]之后將結果序列化為字符串回復給調用者
總結
以上是生活随笔為你收集整理的【项目介绍】搜索引擎的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 【项目介绍】FTP服务器
- 下一篇: 海量数据处理(二) :常见海量数据处理方
