日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【项目介绍】搜索引擎

發(fā)布時(shí)間:2024/4/11 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【项目介绍】搜索引擎 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • BoostSearch / 搜索引擎
    • 開發(fā)語言
    • 開發(fā)環(huán)境
    • 依賴庫
    • 項(xiàng)目簡介
    • 項(xiàng)目特點(diǎn)
    • 項(xiàng)目鏈接
    • 演示
  • 項(xiàng)目介紹與難點(diǎn)分析
    • 解析
    • 分詞
    • 索引
      • 正排索引
      • 倒排索引
    • 權(quán)重
    • 包裝


BoostSearch / 搜索引擎

開發(fā)語言

C/C++,HTML,CSS,JavaScript

開發(fā)環(huán)境

CentOS7、vim、g++、gdb、git、Makefile

依賴庫

jsoncpp, cppjieba, http-lib, boost

項(xiàng)目簡介

BoostSearch是一個(gè)基于boost文檔的站內(nèi)搜索引擎,當(dāng)用戶在頁面上輸入查詢詞后,就會快速的查詢出相關(guān)的boost在線文檔,彌補(bǔ)了boost在線文檔中沒有搜索的缺陷。

項(xiàng)目特點(diǎn)

  • 對離線版本的html文檔進(jìn)行解析,將解析結(jié)果整理為一個(gè)行本文文件。
  • 讀取處理好的行文本文件進(jìn)行分詞、權(quán)重計(jì)算等操作,在內(nèi)存中構(gòu)造出正排索引和倒排索引。
  • 對查詢詞進(jìn)行分詞、觸發(fā),依據(jù)相關(guān)度對查詢結(jié)果進(jìn)行排序,并以Json格式進(jìn)行包裝后序列化為字符串返回。
  • 通過HTTP服務(wù)器搭載搜索頁面, 為外部提供服務(wù)。

項(xiàng)目鏈接

github

演示


項(xiàng)目介紹與難點(diǎn)分析

具體的模塊劃分以及功能分析我以上圖的形式展現(xiàn)了出來,本文主要說明項(xiàng)目中的一些難點(diǎn)以及問題分析,至于具體實(shí)現(xiàn)以及其他內(nèi)容請參考源代碼
https://github.com/HONGYU-LEE/BoostSearch

解析

無論是站內(nèi)搜索引擎還是全網(wǎng)搜索引擎,第一步都必須對獲取的文件進(jìn)行一個(gè)預(yù)處理

預(yù)處理分為兩個(gè)步驟

  • 找出所有的HTML文檔
  • 提取出文檔中的關(guān)鍵信息
  • 無論是官方提供的文檔文件,還是通過爬蟲在網(wǎng)上爬取的文件,都會夾雜著許多除了HTML外的例如圖片、音樂、測試代碼等搜索引擎不需要的東西,所以第一步就是將從這些雜亂的文件中,找出所有的HTML。

    而C++標(biāo)準(zhǔn)庫中并沒有提供一個(gè)能夠遍歷文件的方法,而linux的系統(tǒng)函數(shù)dirent也可以做到,但是在我上一個(gè)項(xiàng)目MiniFTP中獲取列表那一塊可以看到,這玩意并不好用,所以這里我用的是Boost庫中的filesystem模塊。它會通過一個(gè)迭代器來遞歸找到該目錄下的所有文件,來達(dá)到遍歷的效果

    找到文件后,第二步就是將文章中的關(guān)鍵信息提取出來

    我們需要的信息只有三個(gè),標(biāo)題、URL、正文

    標(biāo)題:標(biāo)題的提取方法很簡單,只需要找到HTML中title這個(gè)標(biāo)簽中的內(nèi)容即可

    URL:URL的處理也很簡單,通過查詢官方在線文檔,可以看到URL的構(gòu)成就是官方文檔地址加上文件名,所以我們只需要提取出之前枚舉的文件的名字,將其和文檔地址進(jìn)行組合,就可以獲得到URL

    正文:接下來我們需要做的,就是過濾掉所有的標(biāo)簽以及標(biāo)簽中的內(nèi)容,剩下的text內(nèi)容就是我們需要的正文。并且為了之后方便處理,將所有的換行符全部替換成為空格,將每篇的正文都單獨(dú)處理成一行。

    提取出信息后,就需要對信息進(jìn)行劃分并保存。

    我們所要做的,就是將每一篇文檔的信息全部匯總到一行中,即處理為一個(gè)行文本文件,使得每行數(shù)據(jù)就對對應(yīng)著一篇文檔,這樣我們在后續(xù)處理的時(shí)候只需要按行來提取出信息,就可以達(dá)到遍歷所有數(shù)據(jù)的效果。

    接著,還需要通過某種方式來劃分開標(biāo)題、URL、正文

    考慮到這是一個(gè)行文本文件,換行、空格、回車等符號則直接pass,并且作為分隔符的符號必須要在文章中沒有出現(xiàn)過,考慮到這一點(diǎn),就可以使用不可見字符(文章中不可能出現(xiàn)的字符)

    在這里我選擇的是\1(SOH符號),通過這個(gè)符號對所有的標(biāo)題、URL、正文進(jìn)行劃分。

    ?

    搜索的本質(zhì)其實(shí)就是找到出現(xiàn)過用戶查詢內(nèi)容的文章

    要做到這一點(diǎn)其實(shí)并不復(fù)雜,可以通過比對文章內(nèi)容以及用戶查詢詞來完成。

    所以這時(shí)需要進(jìn)行兩個(gè)步驟,一個(gè)是分詞,一個(gè)是索引

    分詞

    分詞很好理解,就是將用戶輸入的字符串,分解成為一個(gè)一個(gè)的關(guān)鍵詞,然后再通過關(guān)鍵詞去進(jìn)行內(nèi)容的查找。

    例如對“今天的天氣很好啊”這句話進(jìn)行分詞

    今天/的/天氣/很好/啊

    對于我們?nèi)藖碚f,由于我們有自主思考的能力,加上了出生至今的經(jīng)驗(yàn)以及積累,分詞對于我們來說是很簡單的一件事,但是對于計(jì)算機(jī)而言,這并不是一件簡單的事情。

    例如這句出自神雕俠侶中的一句話,需要結(jié)合其中的背景來理解,機(jī)器則很難理解

    “我也想過過兒過過的生活” = 我/也想/過/過兒/過/過的/生活

    并且有些句子可能會解析出多種結(jié)果

    “在北京大學(xué)生活區(qū)喝進(jìn)口紅酒”

    1.在/北京/大學(xué)/生活區(qū)/喝/進(jìn)口/紅酒

    2.在/北京大學(xué)/生活區(qū)/喝/進(jìn)口紅酒

    3.在/北京/大學(xué)生活區(qū)/喝/進(jìn)口紅酒

    并且在句子中會經(jīng)常出現(xiàn)一些如“的”,“了”,“呢”,“嗎”,"像"等等等語氣詞或者結(jié)束詞會不可避免的大量出現(xiàn),但是這些詞又沒有什么實(shí)際上的意義,所以我們將其稱為**“暫停詞/停用詞”**,不會將其放入權(quán)重的計(jì)算中。

    考慮到分詞的困難巨大,甚至比我們整個(gè)項(xiàng)目都要復(fù)雜,所以這里就不再自己造輪子,而是引入了第三方庫結(jié)巴分詞——https://github.com/yanyiwu/cppjieba

    索引

    接著就到了整個(gè)項(xiàng)目中最核心的部分,索引。

    即使本項(xiàng)目是一個(gè)站內(nèi)的搜索引擎,只有8000多篇文檔, 但是實(shí)際算下來字符數(shù)量可能多達(dá)千萬甚至更多,如果直接進(jìn)行遍歷搜索,那么速度將會慢的嚇人。而在龐大的數(shù)據(jù)中想要快速的查詢到指定的數(shù)據(jù),那么馬上就能想到的方法就是建立索引

    索引就相當(dāng)與我們看書時(shí)文章的一個(gè)目錄,我們可以通過目錄上的頁數(shù)來快速定位到需要查詢的數(shù)據(jù),所以我們以下兩種索引,正排索引倒排索引

    正排索引

    正排索引的作用即根據(jù)文檔ID來查找文檔信息

    //文章信息 struct DocInfo {int64_t doc_id; //文檔IDstring title; //文檔標(biāo)題string url; //文檔URLstring content; //文檔正文 };

    因?yàn)槲覀冏罱K需要查詢的結(jié)果只需要文檔的標(biāo)題、URL、正文,所以正排索引所存儲的文檔信息就使用上面這種結(jié)構(gòu)。

    vector<DocInfo> forward_index; //正排索引

    接著,就需要建立起ID與文檔信息的映射關(guān)系,由于文檔本身并沒有規(guī)定固定的ID,所以這里我就直接使用數(shù)組下標(biāo)來作為文檔的ID,將其以vector的形式進(jìn)行存儲(如果規(guī)定了ID就使用unordered_map)

    倒排索引

    倒排索引的作用即根據(jù)文檔片段來查找相關(guān)的文檔ID

    //權(quán)重信息 struct Weight {int64_t doc_id; //文檔IDint weight; //權(quán)重string word; //關(guān)鍵詞 }; typedef vector<Weight> InvertedList; //倒排拉鏈

    我使用上面的Weight結(jié)構(gòu)來存儲每個(gè)關(guān)鍵詞所對應(yīng)出現(xiàn)的文章與該文章的權(quán)重大小。

    由于一個(gè)Weight對應(yīng)著該詞的其中一篇相關(guān)文章,所以為了方便查詢,我將該詞的所有相關(guān)文章以一個(gè)vector進(jìn)行存儲,構(gòu)成一個(gè)倒排拉鏈的結(jié)構(gòu),并將這個(gè)倒排拉鏈作為倒排索引的節(jié)點(diǎn)。這樣通過關(guān)鍵詞來查找到倒排拉鏈時(shí),就可以通過直接遍歷倒排拉鏈來獲取到所有的相關(guān)文檔。

    unordered_map<string, InvertedList> invert_index; //倒排索引

    關(guān)鍵詞與相關(guān)文檔的映射使用哈希表(unordered_map)建立

    下面介紹下索引的建立流程

    假設(shè)我們此時(shí)有兩篇文章,首先進(jìn)行分詞

    接著,根據(jù)文檔信息構(gòu)建出正排索引

    文檔ID——》文檔信息

    接著,依據(jù)每個(gè)關(guān)鍵詞出現(xiàn)過的文章,構(gòu)建出倒排索引

    關(guān)鍵詞——》相關(guān)文檔ID

    建立了這樣一個(gè)索引體系之后,在查詢時(shí),我們只需要對查詢詞進(jìn)行分詞,再通過倒排索引查詢到對應(yīng)的倒排拉鏈,權(quán)重信息,來為每個(gè)倒排拉鏈的文檔進(jìn)行排序,就可以根據(jù)相關(guān)度高低以此獲取到對應(yīng)的文檔ID,再通過正排索引就可以獲取到我們需要的文檔信息了。

    權(quán)重

    由于我們存儲的數(shù)據(jù)量不是很大,只有大概8000多個(gè)文檔,并且該項(xiàng)目只是用于學(xué)習(xí)搜索引擎原理,所以這里就使用線性公式來進(jìn)行權(quán)重的計(jì)算,計(jì)算的依據(jù)就是關(guān)鍵詞的詞頻

    首先用以下結(jié)構(gòu)來統(tǒng)計(jì)關(guān)鍵詞在文章的正文以及標(biāo)題中分別出現(xiàn)的次數(shù)

    struct WordCount {WordCount() : title_count(0), content_count(0){} int title_count; //標(biāo)題出現(xiàn)頻率int content_count; //正文出現(xiàn)頻率 };

    關(guān)鍵詞在正文中和標(biāo)題中出現(xiàn)的權(quán)重應(yīng)該不一樣。

    因?yàn)闃?biāo)題即對正文的高度概括,在標(biāo)題中出現(xiàn)的內(nèi)容往往與實(shí)際內(nèi)容相關(guān),而在正文即使出現(xiàn),也有可能只是順口一提或者引用,所以我認(rèn)為在標(biāo)題中出現(xiàn)的權(quán)重應(yīng)該要比在正文出現(xiàn)要高

    所以推導(dǎo)的公式如下:權(quán)重 = 標(biāo)題出現(xiàn)次數(shù) * 10 + 正文出現(xiàn)次數(shù)

    //權(quán)重 = 標(biāo)題出現(xiàn)次數(shù) * 10 + 正文出現(xiàn)次數(shù) weight.weight = word_pair.second.title_count * 10 + word_pair.second.content_count;

    包裝

    對于查詢的結(jié)果,由于我們并不需要將所有的信息都展示在搜索頁面上,只需要展示關(guān)鍵信息,所以我們需要的內(nèi)容只有標(biāo)題、URL、描述。

    前兩個(gè)已經(jīng)獲取了,但是描述還需要我們自己從正文中提取。

    關(guān)于描述信息的提取,我的思路如下。

  • 查詢關(guān)鍵詞在文章中第一次出現(xiàn)的位置,獲取接近這個(gè)位置的前后內(nèi)容當(dāng)作描述信息
  • 因?yàn)榇蟛糠置枋鲂畔⒌囊?guī)律都是先提出關(guān)鍵詞,然后對關(guān)鍵詞進(jìn)行解釋,所以我們盡量保證關(guān)鍵詞的位置在描述片段中靠前
  • 描述信息要簡短,如果內(nèi)容過長則用省略號代替
  • 如果關(guān)鍵詞未找到,則說明關(guān)鍵詞在標(biāo)題中,直接從頭截取一段內(nèi)容當(dāng)作描述
  • 根據(jù)以上條件,我推導(dǎo)出以下描述信息提取公式

    返回內(nèi)容 = 150字節(jié) = 關(guān)鍵詞往前50字節(jié) + 關(guān)鍵詞(包括關(guān)鍵詞)往后100個(gè)字節(jié)

  • 正常情況下,選擇關(guān)鍵詞位置的前50個(gè)字節(jié)的位置作為起始位置,返回起始位置往后的150個(gè)字節(jié)。
  • 當(dāng)前面不足50個(gè)時(shí),就從起點(diǎn)位置往后直接返回150個(gè)字節(jié)。當(dāng)末尾不足時(shí),直接返回到結(jié)束位置。如果后面還有數(shù)據(jù)用省略號表示。
  • 如果正文中未找到關(guān)鍵詞,則說明關(guān)鍵詞在標(biāo)題中,直接從起始位置開始返回150字節(jié)
  • 這樣,我們就可以構(gòu)建出最后查詢出的結(jié)果。

    但是我們最終查詢的結(jié)果并不能直接返回給調(diào)用者,還需要對其進(jìn)行包裝

    這里我采用的是JSON格式,借助了第三方庫JSONCPP(https://github.com/open-source-parsers/jsoncpp)來進(jìn)行包裝

    JSON的格式如下

    [{"title" : "標(biāo)題","url" : "url","desc" : "描述"}{"title" : "標(biāo)題","url" : "url","desc" : "描述"}{"title" : "標(biāo)題","url" : "url","desc" : "描述"}.................. ]

    之后將結(jié)果序列化為字符串回復(fù)給調(diào)用者

    總結(jié)

    以上是生活随笔為你收集整理的【项目介绍】搜索引擎的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。