如何下载别人的网站做模板,东莞网站建设套餐,网站页面风格分类,如何做app软件开发BOOST
Boost是一个由C社区开发的开源库#xff0c;为C语言标准库提供扩展。这个库由C标准委员会库工作组成员发起#xff0c;旨在提供大量功能和工具#xff0c;帮助C开发者更高效地编写代码。Boost库强调跨平台性和对标准C的遵循#xff0c;因此与编写平台无关#xff0…BOOST
Boost是一个由C社区开发的开源库为C语言标准库提供扩展。这个库由C标准委员会库工作组成员发起旨在提供大量功能和工具帮助C开发者更高效地编写代码。Boost库强调跨平台性和对标准C的遵循因此与编写平台无关是C标准化进程的重要开发引擎之一。
但是BOOST没有站内的搜索引擎我们在使用想快速查找信息的时候十分不方便所以我们自己动手实现一个。
技术栈与项目环境
技术栈
用C/C作为主要编程语言并利用STL中的容器等功能cpp-httplib轻量级HTTP库用于构建HTTP服务器和处理HTTP请求cppjieba中文分词工具用于对查询词和文档进行分词处理。HTML/CSS用于前端开发构建用户搜索界面和展示搜索结果。
项目环境
CentOS7云服务器作为项目的运行环境vim/gcc/g/Makefile代码编辑、构建
搜索引擎的原理 正排索引Forward Index
正排索引是以文档的ID为关键字表中记录文档中每个词的位置信息。
文档ID文档内容1张三在吃饭2张三在买东西
索引的ID和文档的内容是一一对应的记录了文档中出现的关键词及其出现次数和位置信息。正排索引的优势在于可以快速地查找某个文档里包含哪些词项但不适用于查找包含某个词项的文档有哪些。
倒排索引Inverted Index
倒排索引是以词为关键字的索引结构表中记录了出现这个词的所有文档的ID和位置信息或者记录了这个词在哪些文档的哪些位置出现过以及出现多少次。
关键字(具有唯一性)文档IDweight(权重)张三文档1,文档2吃饭文档1买东西文档2
查找过程: 用户输入张三- 倒排索引-提取文档ID(1,2)-根据正排索引-找到文档的内容 -title conent(desc)url 文档结果进行摘要 -构建响应结果
项目实现 数据部分
在boost官网把boost的内容下载下来并在CentOS7下解压。 创建一个data的目录把boost_1_83_0/doc/html/data拷贝到data目录下的input中来此时data/input就是我们的数据源。 去标签与数据清洗Parser
上面说到的data/input数据源中我们随便打开一个html文件 可以看到我们需要的是文档中的内容所以需要把标签去掉写入到raw_html目录中。
typedef struct DocInfo
{std::string title; // 文档的标题std::string content; // 文档的内容 --body里面有内容信息std::string url; // 该文档在官网中的url
} DocInfo_t;int main()
{std::vectorstd::string files_list;// 1.递归式的把每个html文件命带路径,保存到files_list中// 方便后期进行一个一个文件的读取if (!EnumFile(src_path, files_list)){std::cerr euum file name error! std::endl;return 1;}// 2.按照files_list 读取每个文件的内容,并进行解析std::vectorDocInfo_t results; // 解析后的结果存放在results中if (!ParseHtml(files_list, results)){std::cerr parse html error std::endl;return 2;}// 3.把解析完毕的各个文件内容,写入到output中,按照\3作为分隔符if (!SaveHtml(results, output)){std::cerr sava html error std::endl;return 3;}return 0;
}遍历数据源下所有以 .html 扩展名的文件路径然后将这些路径存储在一个files_list中。 bool EnumFile(const std::string src_path, std::vectorstd::string *files_list)
{namespace fs boost::filesystem; // 给boost命名空间起别名fs::path root_path(src_path);if (!fs::exists(root_path)) // 路径不存在直接退出{std::cerr src_path not exists std::endl;return false;}// 定义一个迭代器来判断递归的结束fs::recursive_directory_iterator end;// 遍历文件路径for (fs::recursive_directory_iterator iter(root_path); iter ! end; iter){// 判断是不是普通文件if (!fs::is_regular_file(*iter)){continue;}// 判断文件名的后缀是否符合要求if (iter-path().extension() ! .html){continue;}files_list-push_back(iter-path().string()); // 把带路径的html保存在files_list中}return true;
}从files_list的HTML文件列表中解析信息提取titile、continue并构建URL到一个个DocInfo_t中。
bool ParseHtml(const std::vectorstd::string files_list, std::vectorDocInfo_t *results)
{for (const std::string file : files_list){std::string result;// 读文件if (!ns_util::FileUtil::ReadFile(file, result)){continue;}DocInfo_t doc;// 解析文件,提取titleif (!ParseTitle(result, doc.title)){continue;}// 解析文件,提取continue--去标签if (!ParseContent(result, doc.content)){continue;}// 解析文件路径,构建urlif (!ParseUrl(file, doc.url)){continue;}// 完成解析任务,解析结果保存在doc中results-push_back(std::move(doc)); // 减少拷贝}return true;
}//title 我们要的内容 title
static bool ParseTitle(const std::string file, std::string *title)
{std::size_t begin file.find(title);if (begin std::string::npos){return false;}std::size_t end file.find(/title);if (end std::string::npos){return false;}begin std::string(title).size(); if (begin end){return false; }*title file.substr(begin, end - begin);return true;
}/*td aligncentera href../../../index.htmlHome/a/td
td aligncentera href../../../libs/libraries.htmLibraries/a/td
td aligncentera hrefhttp://www.boost.org/users/people.htmlPeople/a/td
td aligncentera hrefhttp://www.boost.org/users/faq.htmlFAQ/a/td
td aligncentera href../../../more/index.htmMore/a/td*/
//上面的标签都清洗掉
/*div classtitlepagedivdivh2 classtitle styleclear: both
a namearray.ack/aAcknowledgements/h2/div/div/div
pDoug Gregor /p*/
//标签中的Acknowledgements 、Doug Gregor保留下来
static bool ParseContent(const std::string file, std::string *content)
{//状态机enum status{LABLE,CONTENT};enum status s LABLE; // 默认为LABLE状态for (char c : file) //以字符方式遍历file{switch (s){case LABLE:if (c ) // 碰到就意味着标签处理完毕s CONTENT;break;case CONTENT:if (c ) //碰到就意味着内容处理完毕s LABLE;else{// 不保留原始文件的\n, \n用来做html解析之后的文本分割符if (c \n)c ;content-push_back(c);}break;default:break;}}return true;
}/*
boost库的官方文档和下载下来的文档是有路径对应关系的
//官网URL
https://www.boost.org/doc/libs/1_83_0/doc/html/accumulators.html
//linux中URL
data/input/accumulators.html//下载下来的boost库url_head https://boost.org/doc/libs/1_83_0/doc/html/
url_tail data/input/accumulators.html data/input(删除)-- /accumulators.html
url url_head url_tail //相当于一个官网连接 */static bool ParseUrl(const std::string file_path, std::string *url)
{std::string url_head https://www.boost.org/doc/libs/1_83_0/doc/html;std::string url_tail file_path.substr(src_path.size()); //subsrt去掉data/input*url url_head url_tail;return true;
}将一组DocInfo_t类型的数据也就是上面解析出来的内容保存到output中。
#define SEP \3 //分割符号
bool SaveHtml(const std::vectorDocInfo_t results, const std::string output)
{// 二进制写入--写入什么文档保存就是什么std::ofstream out(output, std::ios::out | std::ios::binary); if (!out.is_open()){std::cerr open output failed std::endl;return false;}//title\3content\3url \n title\3content\3url \n ....//方便getline(ifsream,line)直接获取文档全部内容 title\3content\3url(getline以\n结束) for (auto item : results){std::string out_string;out_string item.title;out_string SEP;out_string item.content;out_string SEP;out_string item.url;out_string \n; // 文档和文档之间的分隔out.write(out_string.c_str(), out_string.size());}out.close();return true;
}
索引Index
对清洗过的内容构建正排、倒排索引
struct DocInfo
{ std::string title; //文档的标题std::string content; //文档对应的去标签之后的内容std::string url; //官网文档urluint64_t doc_id; //文档的ID,用uint64_t:为了防止索引越界
};//倒排结构
struct InvertedElem
{ uint64_t doc_id;//对应的文档idstd::string word;//关键字int weight; //权重 可以根据权重决定文档显示的先后顺序InvertedElem():weight(0){}
};typedef std::vectorInvertedElem InvertedList;// 倒排拉链
//单例模式
class Index
{
private:std::vectorDocInfo forward_index;// 正排拉链//存放关键字和倒排拉链的映射关系std::unordered_mapstd::string, InvertedList inverted_index;
public:Index(){} //但是一定要有函数体不能delete~Index(){}Index(const Index) delete;Index operator(const Index) delete;static Index* instance;//指向全局唯一的单例对象static std::mutex mtx;
public://获取全局唯一的单例对象static Index* GetInstance(){//第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁//特点第一次加锁,后面不加锁,保护线程安全,同时提高效率if(nullptr instance){//加锁只有第一次有意义,后面再有线程来没必要加锁,加锁会引发效率低下mtx.lock();if(nullptr instance){instance new Index();}mtx.unlock();}return instance;//返回这个全局的单例对象}//根据文档id找到找到文档内容 DocInfo* GetForwardIndex(uint64_t doc_id) { if(doc_id forward_index.size()) //没有该文档id{std::cerr doc_id out range, error! std::endl;return nullptr;}return forward_index[doc_id]; //数组的下标天然就是文档id,所以直接返回数组对应的内容的地址}//根据关键字word获得倒排拉链 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);}//根据文档内容构造索引,参数:经过数据清洗之后的文档bool BuildIndex(const std::string input) {std::ifstream in(input, std::ios::in | std::ios::binary);if(!in.is_open()){ std::cerr build index error, open input failed std::endl;return false;}int count 0;//方便观察当前建立的索引个数std::string line; //读取一行数据 //对每一个html文件格式化之后的内容进行正排和倒排索引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 % 150 0){LOG(NORMAL,当前的已经建立的索引文档:std::to_string(count));}}return true;}
};建立正排索引 //line:就是一个html文件里面去标签,格式化之后的文档内容DocInfo *BuildForwardIndex(const std::string line) {//1. 解析line-本质是进行字符串切分,解析成:title, content, url std::vectorstd::string results;//保存切分好的内容const std::string sep \3; //分隔符//切分字符串ns_util::StringUtil::Split(line, results, sep);if(results.size() ! 3) //判断切分结果是否正确,要切分为3部分{ return nullptr;}//2. 切分好的字符串进行填充到DocIinfoDocInfo doc;doc.title results[0]; //titledoc.content results[1]; //contentdoc.url results[2]; ///urldoc.doc_id forward_index.size(); //3. 插入到正排索引数组当中forward_index.push_back(std::move(doc)); return forward_index.back(); }建立倒排索引 bool BuildInvertedIndex(const DocInfo doc)//建立倒排索引,参数是正排索引节点{//DocInfo里面包含一个html文件的:{title, content, url, doc_id}struct word_cnt //针对一个词的数据统计{ int title_cnt;//在标题出现次数int content_cnt;//在内容中出现次数word_cnt():title_cnt(0), content_cnt(0){} };std::unordered_mapstd::string, word_cnt word_map; //暂存词频的映射表/* 根据文档标题和内容,分词并进行词频统计 *///对标题进行分词std::vectorstd::string title_words;ns_util::JiebaUtil::CutString(doc.title, title_words);//对标题分词之后的结果进行词频统计for(std::string s : title_words){ boost::to_lower(s); //忽略大小写//查找对应关键词,如果存在就不存在就新建 word_map[s].title_cnt; }//对内容进行分词std::vectorstd::string content_words;ns_util::JiebaUtil::CutString(doc.content, content_words);//对内容进行词频统计for(std::string s : content_words) {boost::to_lower(s);//忽略大小写word_map[s].content_cnt;}// 自定义相关性#define X 15 //标题当中出现的词,权重更高#define Y 1//建立倒排拉链for(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; InvertedList inverted_list inverted_index[word_pair.first];inverted_list.push_back(std::move(item));}return true;}搜索服务Searcher
前面的部分都是前期的准备工作Searcher提供搜索服务对用户输入的字符串进行分词使用倒排索引查找与查询词相关的内容并进行排序。
namespace ns_searcher
{struct InvertedElemPrint{uint64_t doc_id; // 文档idint weight; // 累加权重std::vectorstd::string words; // 一个doc_id对应多个关键字InvertedElemPrint() : doc_id(0), weight(0){}};class Searcher{private:ns_index::Index *index; // 供系统进行查找的索引public:Searcher() {}~Searcher() {}void InitSearcher(const std::string input){// 1. 获取或者创建index对象index ns_index::Index::GetInstance();LOG(NORMAL, 获取index单例成功);// 2. 根据index对象建立索引index-BuildIndex(input);LOG(NORMAL, 建立正排和倒排索引成功...);}// 获取一部分内容std::string GetDesc(const std::string html_content, const std::string word){// 1.在html_content范围内,找word首次出现的位置auto cmp [](char x, char y) { return (std::tolower(static_castunsigned char(x)) std::tolower(static_castunsigned char(y))); }; auto iter std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), cmp); if (iter html_content.end()){return None1;}int pos iter - html_content.begin();// 2. 获取start,end的位置int start 0; // 默认就是在文本的起始位置int end html_content.size() - 1; // 默认是文本的结尾// 找到word在html_content中的首次出现的位置,然后往前找30字节,如果没有,就从 start开始// 往后找80字节如果没有,到end就可以的 ,然后截取出这部分内容const int prev_step 30;const int next_step 80;if (pos - prev_step 0)start pos - prev_step;if (pos next_step end)end pos next_step;// 3. 截取子串,然后returnif (start end)return None2;// 从start开始,截取end - start个字节的内容std::string desc html_content.substr(start, end - start);desc ...;return desc;}// query:用户搜索关键字 json_string: 返回给用户浏览器的搜索结果void Search(const std::string query, std::string *json_string){// 1.对query进行按照searcher的要求进行分词std::vectorstd::string words;ns_util::JiebaUtil::CutString(query, words);// 2.就是根据分词的各个词进行index索引查找std::vectorInvertedElemPrint inverted_list_all; // 存放所有经过去重之后的倒排拉链// 根据doc_id进行去重,凡是id相同的倒排索引节点,其关键字都放在InvertedElemPrint的vector里面std::unordered_mapuint64_t, InvertedElemPrint tokens_map;// 因为我们建立索引的时候,建立index是忽略大小写所以搜索的时候关键字也需要忽略大小写for (std::string word : words){boost::to_lower(word); // 将切分之后的查找关键字转为小写// 先查倒排ns_index::InvertedList *inverted_list index-GetInvertedList(word); // 通过关键字获得倒排拉链if (nullptr inverted_list) {continue; // 检测下一个关键词}for (const auto elem : *inverted_list) // 遍历倒排拉链,合并索引节点{//[]:如果存在直接获取如果不存在新建auto item tokens_map[elem.doc_id]; // 根据文档id获得其对应的关键字集合// 此时item一定是doc_id相同的倒排拉链打印节点item.doc_id elem.doc_id;item.weight elem.weight; // 权值累加item.words.push_back(elem.word); // 把当前的关键字放到去重的倒排拉链打印节点当中的vector当中保存}}for (const auto item : tokens_map) // 遍历去重之后的map{inverted_list_all.push_back(std::move(item.second)); // 插入倒排拉链打印节点到inverted_list_all}// 3.汇总查找结果按照相关性(weight)进行降序排序auto cmp [](const InvertedElemPrint e1, const InvertedElemPrint e2){return e1.weight e2.weight;};std::sort(inverted_list_all.begin(), inverted_list_all.end(), cmp);// 4.根据查找出来的结果构建json串 -- jsoncpp --通过jsoncpp完成序列化反序列化Json::Value root;for (auto item : inverted_list_all){// item就是倒排索引打印节点ns_index::DocInfo *doc index-GetForwardIndex(item.doc_id); // 根据文档id-查询正排,返回正排索引节点if (nullptr doc){continue;}// doc里面就有title,url,content,文档id// 我们想给浏览器返回的是网页的title,内容摘要,链接Json::Value elem;elem[title] doc-title;//只需要一部分文档内容elem[desc] GetDesc(doc-content, item.words[0]);elem[url] doc-url;// for deubg, for deleteelem[id] (int)item.doc_id; // doc_id是uint64 ,json可能报错,所以转为intelem[weight] item.weight; // doc_id,weight虽然是int,但是json会帮我们自动转为stringroot.append(elem);}// Json::StyledWriter writer; //为了方便调试观看Json::FastWriter writer;*json_string writer.write(root);}};
}http_server
监听HTTP请求解析用户输入的参数调用Searcher进行搜索把给结果响应回给客户。
#include cpp-httplib/httplib.h
#include searcher.hpp//引入搜索引擎
const std::string input data/raw_html/raw.txt;//html文件经过parser之后存放的结果的路径
const std::string root_path ./wwwroot; //wwroot作为服务器的主页int main()
{ns_searcher::Searcher search;search.InitSearcher(input);httplib::Server svr;svr.set_base_dir(root_path.c_str());svr.Get(/s, [search](const httplib::Request req, httplib::Response rsp) {if (!req.has_param(word)){rsp.set_content(必须要有搜索关键字!, text/plain; charsetutf-8);return;}std::string word req.get_param_value(word);LOG(NORMAL,用户在搜索word);std::string json_string;search.Search(word, json_string);//返回给客户端rsp.set_content(json_string, application/json); });LOG(NORMAL,服务器启动成功...);svr.listen(0.0.0.0, 8081);return 0;
}
工具util
//文件读取
class FileUtil{public:static bool ReadFile(const std::string file_path, std::string *out){std::ifstream in(file_path, std::ios::in); // 读文件if (!in.is_open()){std::cerr open file file_path error std::endl;return false;}std::string line;// 为什么getline的返回值是流的引用,while是bool,能进行判断呢// 重载了强制类型转换while (std::getline(in, line)){*out line;}in.close();return true;}};// 字符串切割class StringUtil{public:static void Split(const std::string target, std::vectorstd::string *out, std::string sep){// token_compress_on on:压缩打开 --将所有相连的分隔符压缩成一个 aa\3\3\3\3\3bbb--aa bbb// off:压缩关闭boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);}};//Jieba分词
// dict路径const char *const DICT_PATH ./dict/jieba.dict.utf8;const char *const HMM_PATH ./dict/hmm_model.utf8;const char *const USER_DICT_PATH ./dict/user.dict.utf8;const char *const IDF_PATH ./dict/idf.utf8;const char *const STOP_WORD_PATH ./dict/stop_words.utf8;class JiebaUtil{private:// static cppjieba::Jieba jieba;cppjieba::Jieba jieba;std::unordered_mapstd::string, bool stop_words;private:JiebaUtil() : jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH){}JiebaUtil(const JiebaUtil ) delete;static JiebaUtil *instance;public:static JiebaUtil *get_instance(){static std::mutex mtx;if (nullptr instance){mtx.lock();if (nullptr instance){instance new JiebaUtil();instance-InitJiebaUtil();}mtx.unlock();}return instance;}// 把暂停词加载进来void InitJiebaUtil(){std::ifstream in(STOP_WORD_PATH);if (!in.is_open()){LOG(FATAL, load stop words file error);return;}std::string line;while (std::getline(in, line)){stop_words.insert({line, true});}in.close();}
/*去掉暂停词暂停词是对我们搜索没意义的词语如“的”、“地”、“吧”等*/void CutStringHelper(const std::string src, std::vectorstd::string *out){jieba.CutForSearch(src, *out);for (auto iter out-begin(); iter ! out-end();){auto it stop_words.find(*iter);if (it ! stop_words.end()){ // 说明当前的string 是暂停词需要去掉iter out-erase(iter);}else{iter;}}}public:static void CutString(const std::string src, std::vectorstd::string *out){ns_util::JiebaUtil::get_instance()-CutStringHelper(src, out);// jieba.CutForSearch(src,*out);}};
log
#pragma once
#includeiostream
#includestring
#includectime
#define NORMAL 1
#define WARNING 2
#define DEBUG 3
#define FATAL 4
#define LOG(LEVEL,MESSAGE) log(#LEVEL,MESSAGE,__FILE__,__LINE__)void log(std::string level,std::string message,std::string file,int line)
{std::cout[level]time(nullptr)[message][file:line]std::endl;
}
编写js
用原生js成本高,jQuery好
scriptfunction Search(){// 是浏览器的一个弹出框//alert(hello js!);// 1. 提取数据, $可以理解成就是JQuery的别称let query $(.container .search input).val();console.log(query query); //console是浏览器的对话框可以用来进行查看js数据//2. 发起http请求,ajax: 属于一个和后端进行数据交互的函数JQuery中的$.ajax({type: GET,url: /s?word query,success: function(data){console.log(data);BuildHtml(data);}});}function BuildHtml(data){// if(data||dataull)// {// document.write(搜索不到);// return;// }// 获取html中的result标签let result_lable $(.container .result);// 清空历史搜索结果result_lable.empty();for( let elem of data){ let a_lable $(a, {text: elem.title,href: elem.url,// 跳转到新的页面target: _blank});let p_lable $(p, {text: elem.desc});let i_lable $(i, {text: elem.url});let div_lable $(div, {class: item});a_lable.appendTo(div_lable);p_lable.appendTo(div_lable);i_lable.appendTo(div_lable);div_lable.appenesult_lable);}}/script项目扩展
建立整个站的搜索把boost库的各个版本都进行正排倒排建立索引数据部分设计一个在线更新网页内容利用爬虫信号等进行设计添加整站搜索、竞价排名、热词统计等进一步丰富项目的功能和用户体验。设计登录注册功能引入mysql
遇到问题
1.存在搜索出相同的内容的问题
解决在searcher模块用unordered_map对建立的倒排拉链进行去重。
2.查看权重值的时候发现对不上
原因分词的时候会出现包含单词也会统计的情况比如我搜sprlitsprlitabc也会被搜到。
3.使用jieba库的时候出现找不到Log的错误
jieba的include下的头文件有方法jieba/dict里有分词的库具体怎么分词我们采取链接的方式jieba有一个问题就是需要把deps/limonp拷贝到include/cppjieba下不然会报错找不到log