好的做淘寶詳情頁(yè)的網(wǎng)站有哪些內(nèi)容貼吧友情鏈接在哪
? ? ? ? 如果想使“用戶搜索內(nèi)容”和“網(wǎng)頁(yè)文件內(nèi)容”之間產(chǎn)生聯(lián)系,就應(yīng)該將“用戶搜索內(nèi)容”和“網(wǎng)頁(yè)文件”分為很小的單元 (這個(gè)單元就是關(guān)鍵詞),尋找用戶搜索單元是否出現(xiàn)在這個(gè)文檔之中,如果出現(xiàn)就證明這個(gè)網(wǎng)頁(yè)文件和用戶搜索內(nèi)容有關(guān)系,如果該搜索單元在這篇文章中出現(xiàn)的次數(shù)較高,也就證明:這篇文章與搜索內(nèi)容有很強(qiáng)的相關(guān)性,這就是權(quán)值(weight)。
? ? ? ? 權(quán)值可以自己定義:比如標(biāo)題出現(xiàn)一次對(duì)應(yīng)的權(quán)值為10,內(nèi)容出現(xiàn)一次對(duì)應(yīng)的權(quán)值為5,再分別統(tǒng)計(jì)標(biāo)題和文檔內(nèi)容中該搜素單元出現(xiàn)的次數(shù)。總權(quán)值(該搜索單元)= 標(biāo)題出現(xiàn)的次數(shù)*10 +文檔內(nèi)容出現(xiàn)的次數(shù)*5;再將用戶所有的搜索單元的總權(quán)值加在一起就是這篇文章與用戶搜索內(nèi)容的相關(guān)性。我們可以通過每一篇文檔的權(quán)值去進(jìn)行排序,給用戶呈現(xiàn)出最想要的文檔內(nèi)容。
? ? ? ? 如何去存儲(chǔ)這些網(wǎng)頁(yè)文檔內(nèi)容呢?
? ? ? ? 網(wǎng)頁(yè)文檔內(nèi)容有 標(biāo)題,網(wǎng)頁(yè)文檔內(nèi)容 url網(wǎng)址三個(gè)部分。所以就需要結(jié)構(gòu)體將他們組織在一起。我們可以選擇線性容器進(jìn)行存儲(chǔ),因?yàn)榫€性容器存儲(chǔ)的位置就可以代表這篇文章的 文檔ID。
? ? ? ? 那么現(xiàn)在面臨的問題就是,用戶搜索單元(用戶搜索關(guān)鍵詞)和文檔單元(文檔關(guān)鍵詞)之間如何建立聯(lián)系。下面采用正排索引和倒排索引去建立它們之間的關(guān)系。
建立索引:
? ? ? ? 什么是正排索引?
? ? ? ? 正排索引就是文檔ID與文檔之間的關(guān)系。
文檔ID | 文檔內(nèi)容 |
0 | 文檔1 |
1 | 文檔2 |
? ? ? ? 正排索引的建立,就是將文檔ID與文檔內(nèi)容之間進(jìn)行直接關(guān)聯(lián)。如上表所示。
? ? ? ? 那問題來了,該如何關(guān)聯(lián)呢?我們可以利用線性表,如數(shù)組,數(shù)組下標(biāo)與文檔ID正好是對(duì)應(yīng)的,我們將解析出來的數(shù)據(jù)進(jìn)行提取,存放到一個(gè)包含 標(biāo)題(title),內(nèi)容(content),url(網(wǎng)址信息)的結(jié)構(gòu)體,再將結(jié)構(gòu)體放到數(shù)組中,這樣就建立好了正排索引。
? ? ? ? 什么是倒排索引?
? ? ? ? 比如用戶搜索 菜雞愛玩,分詞工具將菜雞愛玩分為 菜雞和愛玩,分別用菜雞和愛玩去文檔中找對(duì)應(yīng)的關(guān)鍵詞。再將關(guān)鍵詞存在的 文檔ID 與 搜索關(guān)鍵詞 之間建立關(guān)系。
關(guān)鍵詞(唯一性)(關(guān)鍵詞) | 文檔ID,權(quán)重weigh(倒排索引拉鏈) |
菜雞 | 文檔2,文檔1 |
愛玩 | 文檔2 |
?首先將處理好的數(shù)據(jù)進(jìn)行關(guān)鍵詞分割,用inverted_index(是map容器,map<關(guān)鍵詞,倒排索引拉鏈>)統(tǒng)計(jì)關(guān)鍵詞都出現(xiàn)在那些文檔中,將關(guān)鍵詞出現(xiàn)的這些文檔放進(jìn)倒排索引拉鏈中,這就行形成了關(guān)鍵詞與文檔ID之間的對(duì)應(yīng)關(guān)系。從上面表可以看出,同一個(gè)文檔ID是可以出現(xiàn)在不同的倒排索引拉鏈中的。
然而,剛開始建立索引的過程是有些慢的,很吃系統(tǒng)資源,所以關(guān)于網(wǎng)頁(yè)文檔內(nèi)容太大并且服務(wù)器資源比較少的話,就會(huì)建立失敗,因此前面才會(huì)下載Boost庫(kù)的部分文件,也就是網(wǎng)絡(luò)文件,而不是全部文件。雖然這個(gè)過程慢,但是帶來的好處,還是不小的,因?yàn)樗饕⑦^程是不會(huì)進(jìn)行搜索的,當(dāng)建立好之后,只要你有搜索內(nèi)容,我就去inverted_index的map容器中進(jìn)行查找,找到對(duì)應(yīng)的倒排索引拉鏈,再返回。
當(dāng)搜索關(guān)鍵詞到來時(shí),我就在inverted_index中利用關(guān)鍵詞去找,如果存在這個(gè)關(guān)鍵詞,那所有與這個(gè)關(guān)鍵詞相關(guān)的文檔我都找到了,如果不存在,那真就不存在。
這里的搜索關(guān)鍵詞可能不止一個(gè),搜索者會(huì)輸入一段搜索語(yǔ)句,比如"菜雞愛玩"可能會(huì)被分成“菜”“雞”“菜雞“”愛"“玩""愛玩”等。
正排索引代碼:
DocInfo *BuildForwardIndex(const std::string &line){//1. 解析line,字符串切分//line -> 3 string, title, content, urlstd::vector<std::string> results;const std::string sep = "\3"; //行內(nèi)分隔符ns_util::StringUtil::Split(line, &results, sep);//ns_util::StringUtil::CutString(line, &results, sep);if(results.size() != 3){return nullptr;}//2. 字符串進(jìn)行填充到DocIinfoDocInfo doc;doc.title = results[0]; //titledoc.content = results[1]; //contentdoc.url = results[2]; ///urldoc.doc_id = forward_index.size(); //先進(jìn)行保存id,在插入,對(duì)應(yīng)的id就是當(dāng)前doc在vector中的下標(biāo)!//3. 插入到正排索引的vectorforward_index.push_back(std::move(doc)); //doc,html文件內(nèi)容return &forward_index.back();}
正排索引建立好之后,將構(gòu)建好的結(jié)構(gòu)體返回回去,交給倒排索引進(jìn)行構(gòu)建倒排索引拉鏈。
因?yàn)?span style="color:#fe2c24;">倒排索引的構(gòu)建需要文檔ID,文檔標(biāo)題和文檔內(nèi)容去進(jìn)行關(guān)鍵詞分割,還有權(quán)值的計(jì)算。
注意:這塊不太理解就向后繼續(xù)看,后面整體的構(gòu)建索引會(huì)告訴你為什么這樣做。
獲取正排索引:
//根據(jù)doc_id找到找到文檔內(nèi)容DocInfo *GetForwardIndex(uint64_t doc_id){if(doc_id >= forward_index.size()){std::cerr << "doc_id out range, error!" << std::endl;return nullptr;}return &forward_index[doc_id];
因?yàn)檎潘饕粯?gòu)建了,所以直接利用文檔ID在正排索引拉鏈(存放文檔的結(jié)構(gòu)體數(shù)組)中進(jìn)行查找就可以了。?
什么是權(quán)值?
權(quán)值決定這篇文檔與用戶搜索內(nèi)容之間是否存在關(guān)系以及體現(xiàn)出它們之間相關(guān)性的強(qiáng)弱,因?yàn)槊科恼玛P(guān)于一個(gè)話題的側(cè)重點(diǎn)不一樣,所以我們就用權(quán)值的大小來區(qū)分是否是用戶最想要的,將文檔與搜索關(guān)鍵詞之間的關(guān)系用關(guān)鍵詞出現(xiàn)在標(biāo)題和文檔內(nèi)容中的次數(shù) 和自定義權(quán)值大小 進(jìn)行相關(guān)計(jì)算。
????????比如標(biāo)題出現(xiàn)一次對(duì)應(yīng)的權(quán)值為10,內(nèi)容出現(xiàn)一次對(duì)應(yīng)的權(quán)值為5,再分別統(tǒng)計(jì)標(biāo)題和文檔內(nèi)容中該搜素單元出現(xiàn)的次數(shù)。總權(quán)值(該搜索單元)= 標(biāo)題出現(xiàn)的次數(shù)*10 +文檔內(nèi)容出現(xiàn)的次數(shù)*5;再將用戶所有的搜索單元的總權(quán)值加在一起就是這篇文章與用戶搜索內(nèi)容的相關(guān)性。我們可以通過每一篇文檔的權(quán)值去進(jìn)行排序,給用戶呈現(xiàn)出最想要的文檔內(nèi)容。
你認(rèn)為標(biāo)題與搜索關(guān)鍵詞的相關(guān)性大,就將標(biāo)題的權(quán)值設(shè)置高點(diǎn),同理,文檔內(nèi)容也是一樣的。?
倒排索引代碼:
bool BuildInvertedIndex(const DocInfo &doc){//DocInfo{title, content, url, doc_id}//word -> 倒排拉鏈struct word_cnt{int title_cnt;int content_cnt;word_cnt():title_cnt(0), content_cnt(0){}};std::unordered_map<std::string, word_cnt> word_map; //用來暫存詞頻的映射表//對(duì)標(biāo)題進(jìn)行分詞std::vector<std::string> title_words;ns_util::JiebaUtil::CutString(doc.title, &title_words);//if(doc.doc_id == 1572){// for(auto &s : title_words){// std::cout << "title: " << s << std::endl;// }//}//對(duì)標(biāo)題進(jìn)行詞頻統(tǒng)計(jì)for(std::string s : title_words){boost::to_lower(s); //需要統(tǒng)一轉(zhuǎn)化成為小寫word_map[s].title_cnt++; //如果存在就獲取,如果不存在就新建}//對(duì)文檔內(nèi)容進(jìn)行分詞std::vector<std::string> content_words;ns_util::JiebaUtil::CutString(doc.content, &content_words);//if(doc.doc_id == 1572){// for(auto &s : content_words){// std::cout << "content: " << s << std::endl;// }//}//對(duì)內(nèi)容進(jìn)行詞頻統(tǒng)計(jì)for(std::string s : content_words){boost::to_lower(s);word_map[s].content_cnt++;}#define X 10
#define Y 1//Hello,hello,HELLOfor(auto &word_pair : word_map){InvertedElem item;item.doc_id = doc.doc_id;item.word = word_pair.first;item.weight = X*word_pair.second.title_cnt + Y*word_pair.second.content_cnt; //相關(guān)性InvertedList &inverted_list = inverted_index[word_pair.first];inverted_list.push_back(std::move(item));}return true;}
重點(diǎn)代碼講解:
1 —— InvertedList &inverted_list = inverted_index[word_pair.first];
2 —— inverted_list.push_back(std::move(item));
倒排索引拉鏈inverted_index是一個(gè)map<關(guān)鍵詞,倒排索引拉鏈>,上面代碼第一條就是將關(guān)鍵詞對(duì)應(yīng)的倒排索引拉鏈獲取到,再將新的InvertedElem結(jié)構(gòu)體插到倒排索引拉鏈中。這兩條語(yǔ)句是可以合并的,看起來就會(huì)有些復(fù)雜。
經(jīng)過上述操作于是就成功建立了的關(guān)鍵詞和文檔ID之間的關(guān)系,也就是說,我輸入一段關(guān)鍵詞,用分詞工具將關(guān)鍵詞進(jìn)行分離,用分離的關(guān)鍵詞,在文檔(標(biāo)題,文檔內(nèi)容也進(jìn)行了分詞)中進(jìn)行查找,因?yàn)槭褂昧送惶追衷~工具,所以不會(huì)出現(xiàn),文檔中有該關(guān)鍵詞,而搜不到的情況。
獲取倒排索引拉鏈:
?//根據(jù)關(guān)鍵字string,獲得倒排拉鏈InvertedList *GetInvertedList(const std::string &word){auto iter = inverted_index.find(word);if(iter == inverted_index.end()){std::cerr << word << " have no InvertedList" << std::endl;return nullptr;}return &(iter->second);}?
在倒排索引構(gòu)建好之后,所有的倒排索引拉鏈都存放在inverted_index的map容器中,只需要提供關(guān)鍵詞進(jìn)行查找即可,將找到的倒排索引拉鏈返回出去。
?構(gòu)建索引(整合正排索引和倒排索引的構(gòu)建):
//根據(jù)去標(biāo)簽,格式化之后的文檔,構(gòu)建正排和倒排索引//data/raw_html/raw.txtbool BuildIndex(const std::string &input) //parse處理完畢的數(shù)據(jù)交給我{std::ifstream in(input, std::ios::in | std::ios::binary);if(!in.is_open()){std::cerr << "sorry, " << input << " open error" << std::endl;return false;}std::string line;int count = 0;while(std::getline(in, line)){DocInfo * doc = BuildForwardIndex(line);if(nullptr == doc){std::cerr << "build " << line << " error" << std::endl; //for deubgcontinue;}BuildInvertedIndex(*doc);count++;//if(count % 50 == 0){//std::cout <<"當(dāng)前已經(jīng)建立的索引文檔: " << count <<std::endl;LOG(NORMAL, "當(dāng)前的已經(jīng)建立的索引文檔: " + std::to_string(count));//}}return true;}
首先將處理好的網(wǎng)頁(yè)文件讀取取進(jìn)來,利用std::ifstream類對(duì)文件進(jìn)行相關(guān)操作,因?yàn)槭且?#39;\n'為間隔,將處理好的網(wǎng)頁(yè)文件進(jìn)行了分離,所以就采用getline(in,line)循環(huán)將文件中的數(shù)據(jù)讀取到。
首先建立正排索引,其次再建立倒排索引,因?yàn)榈古潘饕慕⑹腔谡潘饕?/strong>。
單例模式:
Index(){} //但是一定要有函數(shù)體,不能deleteIndex(const Index&) = delete;Index& operator=(const Index&) = delete;static Index* instance;static std::mutex mtx;public:~Index(){}public:static Index* GetInstance(){if(nullptr == instance){mtx.lock();if(nullptr == instance){instance = new Index();}mtx.unlock();}return instance;}
單例模式,就是禁掉這個(gè)類的,拷貝構(gòu)造和賦值重載,讓這個(gè)類不能賦給別人,所有對(duì)象共用一個(gè)instance變量
因?yàn)樵诙嗑€程模式下,會(huì)有很用戶進(jìn)行搜素,需要加把鎖保證臨界區(qū)資源不被破壞。
索引構(gòu)建模塊的整體代碼Index.hpp:
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <unordered_map>
#include <mutex>
#include "util.hpp"
#include "log.hpp"namespace ns_index{struct DocInfo{std::string title; //文檔的標(biāo)題std::string content; //文檔對(duì)應(yīng)的去標(biāo)簽之后的內(nèi)容std::string url; //官網(wǎng)文檔urluint64_t doc_id; //文檔的ID,暫時(shí)先不做過多理解};struct InvertedElem{uint64_t doc_id;std::string word;int weight;InvertedElem():weight(0){}};//倒排拉鏈typedef std::vector<InvertedElem> InvertedList;class Index{private://正排索引的數(shù)據(jù)結(jié)構(gòu)用數(shù)組,數(shù)組的下標(biāo)天然是文檔的IDstd::vector<DocInfo> forward_index; //正排索引//倒排索引一定是一個(gè)關(guān)鍵字和一組(個(gè))InvertedElem對(duì)應(yīng)[關(guān)鍵字和倒排拉鏈的映射關(guān)系]std::unordered_map<std::string, InvertedList> inverted_index;private:Index(){} //但是一定要有函數(shù)體,不能deleteIndex(const Index&) = delete;Index& operator=(const Index&) = delete;static Index* instance;static std::mutex mtx;public:~Index(){}public:static Index* GetInstance(){if(nullptr == instance){mtx.lock();if(nullptr == instance){instance = new Index();}mtx.unlock();}return instance;}//根據(jù)doc_id找到找到文檔內(nèi)容DocInfo *GetForwardIndex(uint64_t doc_id){if(doc_id >= forward_index.size()){std::cerr << "doc_id out range, error!" << std::endl;return nullptr;}return &forward_index[doc_id];}//根據(jù)關(guān)鍵字string,獲得倒排拉鏈InvertedList *GetInvertedList(const std::string &word){auto iter = inverted_index.find(word);if(iter == inverted_index.end()){std::cerr << word << " have no InvertedList" << std::endl;return nullptr;}return &(iter->second);}//根據(jù)去標(biāo)簽,格式化之后的文檔,構(gòu)建正排和倒排索引//data/raw_html/raw.txtbool BuildIndex(const std::string &input) //parse處理完畢的數(shù)據(jù)交給我{std::ifstream in(input, std::ios::in | std::ios::binary);if(!in.is_open()){std::cerr << "sorry, " << input << " open error" << std::endl;return false;}std::string line;int count = 0;while(std::getline(in, line)){DocInfo * doc = BuildForwardIndex(line);if(nullptr == doc){std::cerr << "build " << line << " error" << std::endl; //for deubgcontinue;}BuildInvertedIndex(*doc);count++;//if(count % 50 == 0){//std::cout <<"當(dāng)前已經(jīng)建立的索引文檔: " << count <<std::endl;LOG(NORMAL, "當(dāng)前的已經(jīng)建立的索引文檔: " + std::to_string(count));//}}return true;}private:DocInfo *BuildForwardIndex(const std::string &line){//1. 解析line,字符串切分//line -> 3 string, title, content, urlstd::vector<std::string> results;const std::string sep = "\3"; //行內(nèi)分隔符ns_util::StringUtil::Split(line, &results, sep);//ns_util::StringUtil::CutString(line, &results, sep);if(results.size() != 3){return nullptr;}//2. 字符串進(jìn)行填充到DocIinfoDocInfo doc;doc.title = results[0]; //titledoc.content = results[1]; //contentdoc.url = results[2]; ///urldoc.doc_id = forward_index.size(); //先進(jìn)行保存id,在插入,對(duì)應(yīng)的id就是當(dāng)前doc在vector中的下標(biāo)!//3. 插入到正排索引的vectorforward_index.push_back(std::move(doc)); //doc,html文件內(nèi)容return &forward_index.back();}bool BuildInvertedIndex(const DocInfo &doc){//DocInfo{title, content, url, doc_id}//word -> 倒排拉鏈struct word_cnt{int title_cnt;int content_cnt;word_cnt():title_cnt(0), content_cnt(0){}};std::unordered_map<std::string, word_cnt> word_map; //用來暫存詞頻的映射表//對(duì)標(biāo)題進(jìn)行分詞std::vector<std::string> title_words;ns_util::JiebaUtil::CutString(doc.title, &title_words);//if(doc.doc_id == 1572){// for(auto &s : title_words){// std::cout << "title: " << s << std::endl;// }//}//對(duì)標(biāo)題進(jìn)行詞頻統(tǒng)計(jì)for(std::string s : title_words){boost::to_lower(s); //需要統(tǒng)一轉(zhuǎn)化成為小寫word_map[s].title_cnt++; //如果存在就獲取,如果不存在就新建}//對(duì)文檔內(nèi)容進(jìn)行分詞std::vector<std::string> content_words;ns_util::JiebaUtil::CutString(doc.content, &content_words);//if(doc.doc_id == 1572){// for(auto &s : content_words){// std::cout << "content: " << s << std::endl;// }//}//對(duì)內(nèi)容進(jìn)行詞頻統(tǒng)計(jì)for(std::string s : content_words){boost::to_lower(s);word_map[s].content_cnt++;}#define X 10
#define Y 1//Hello,hello,HELLOfor(auto &word_pair : word_map){InvertedElem item;item.doc_id = doc.doc_id;item.word = word_pair.first;item.weight = X*word_pair.second.title_cnt + Y*word_pair.second.content_cnt; //相關(guān)性InvertedList &inverted_list = inverted_index[word_pair.first];inverted_list.push_back(std::move(item));}return true;}};Index* Index::instance = nullptr;std::mutex Index::mtx;
}
?排序語(yǔ)句是一條lambda表達(dá)式,你也可以寫個(gè)仿函數(shù)傳遞給sort系統(tǒng)函數(shù)。
//4.[構(gòu)建]:根據(jù)查找出來的結(jié)果,構(gòu)建json串 -- jsoncpp --通過jsoncpp完成序列化&&反序列化Json::Value root;for(auto &item : inverted_list_all){ns_index::DocInfo * doc = index->GetForwardIndex(item.doc_id);if(nullptr == doc){continue;}Json::Value elem;elem["title"] = doc->title;elem["desc"] = GetDesc(doc->content, item.words[0]); //content是文檔的去標(biāo)簽的結(jié)果,但是不是我們想要的,我們要的是一部分 TODOelem["url"] = doc->url;//for deubg, for deleteelem["id"] = (int)item.doc_id;elem["weight"] = item.weight; //int->stringroot.append(elem);}//Json::StyledWriter writer;Json::FastWriter writer;*json_string = writer.write(root);