boost文档搜索
功能:实现了boost文档站内搜索的功能,通过输入查询词,可以将和整个查询词有关的文档的网页按该词的出现次数先后排序的显示出来,通过点击可直接进入网页进行文档的阅读。
项目整体框架
一.预处理模块
1.首先我们将boost离线文档下载到本地,然后我们会发现,在boost_1_53_0/doc/html这个目录就是我们需要进行处理的目录,在这个目录里,不仅有html文件还有很多其他的我们不需要的文件,所以我们需要进行一个过滤的操作。这里我们直接使用boost库中的filesystem的操作来进行目录的遍历,将html文档的路径存放在一个vector数组里。
bool enumFile(const string &in,vector<string> *out)
{
//:C++17之前,标准库中,是无法遍历目录的
//:可以借助操作系统的api或者是使用第三方库(boost)
namespace fs=boost::filesystem;
fs::path rootPath(in);
//:判断该目录是否存在
if(!fs::exists(rootPath))
{
cout<<"input path not exists!"<<endl;
return false;
}
//:遍历目录
//:输入目录中,还包含了一些其他的子目录,子目录中可能还有子目录
//:boost中有现成的方式,不必写递归代码,就能完成递归工作
fs::recursive_directory_iterator endIter;//:默认构造函数得到迭代器的end位置
for(fs::recursive_directory_iterator it(rootPath);it!=endIter;++it)
{
//:此处再进行一些判定
//1.是不是普通文件,如果不是,直接过滤
if(!fs::is_regular_file(*it))
{
continue;
}
//2.是不是.html的后缀文件,若不是过滤
if(it->path().extension()!=".html")
{
continue;
}
//:得到的结果便是html文件的路径
out->push_back(it->path().string());
}
return true;
}
2.在上一步中得到了html文档的路径,然后我们需要对文档标题,网址,正文进行解析。
首先我们需要一个构造结构体表示一个文档,在这里面包含了标题,URL,正文
//:解析工作,需要准备一个结构体,表示一个文档
struct Doc
{
//:文档标题,来自html的文件名,最终展示到搜索结果中
string title;
//该文档对应的在线版本的文档地址,落地页的地址(需点击的URL)
string url;
//进行去标签之后得到的html内容
string content;
};
然后我们需要找到html文档中的标题,地址,正文进行解析;
①标题:就是与中间的内容;
②在解析地址的时候我们需要对照在线版本中的网址和我们下载好的本地的离线文档路径,大概就是这样://:离线文档路径形如: ../data/input/html/foreach.html //在线文档路径形如:https://www.boost.org/doc/libs/1_53_0/doc/html/foreach.html
可以发现:只需要在https://www.boost.org/doc/libs/1_53_0/doc后加上/html/foreach.html;
③正文:除了标签之外的内容;
bool parseFile(const string& in,Doc* docInfo)
{
//:整个解析分为三个环节
//读取出文件的内容
string html;
bool ret=common::Util::read(in,&html);
if(!ret)
{
cout<<"read file failed "<<in<<endl;
return false;
}
//1.解析文档的标题(文件名)
ret=parserTitle(html,&docInfo->title);
if(!ret)
{
cout<<"parserTitle failed"<<in<<endl;
return false;
}
//:解析文档的url(文档路径)
ret=parserUrl(in,&docInfo->url);
if(!ret)
{
cout<<"parserUrl failed"<<endl;
return false;
}
//3.解析文档的正文(去html标签)
ret=parserContent(html,&docInfo->content);
if(!ret)
{
cout<<"parserContent failed"<<in<<endl;
return false;
}
return true;
}
最后我们将解析好的内容整理后放到一个行文本文件中,在输出到行文本文件时我们需要用一个假的“\3”为分隔符来解决粘包问题。
二.索引模块
功能:根据之前数据处理的结果,构建正排索引和倒排索引。
1.首先我们还是需要创建一个docInfo数组
//:索引相关代码
//用于构建正排索引
//正排本质上就是一个DocInfo数组
struct DocInfo
{
int64_t docId;
string title;
string url;
string content;
};
2.然后我们还需要创建一个Weight结构体,这里面有给定词word,文档编号,词的权重(通过该词在标题中出现的次数和正文中出现的次数计算而得)
//作为倒排索引的value
//倒排索引也是一个键值对结构,key就是分词结果,value就是weight对象
struct Weight
{
int64_t docId;
int weight;
string word;
};
3.然后我们就可以构建正排索引了:通过给定id编号,对应获取文档内容;构建正排索引我用的容器是vector,vector中使docInfo对象:vector
使用下标作为文档的编号id
倒排索引:通过文档内容获取到文档id,给定一个查询词,获取到和整个查询词相关的文档的顺序排列:倒排索引我用的容器是unorder_map,unordered_map
,key是给定词(string),value是Weight对象的vector(InvertedList);
4.对之前生成的行文本文件的每一行进行切分(boost::split函数),切分好之后将其放到之前构建的正排索引的vector容器中。
5.将切分好的标题,正文进行分词(cppjieba::cutword函数),并统计出其出现次数将其放入一个·unordered_map中unordered_map
三.搜索模块。
1.分词:对查询词进行分词
2.触发:根据分词结果查倒排,找到对应的文档id
如果倒排拉链不是空的,则将这些倒排拉链合并到一个大的数组
为了进行下一步排序,分词的结果可能是多个,其对应的倒排拉链也是多个
需要把N条拉链合并成一个大的拉链(数组),然后再根据数组进行排序
3.排序:根据该词在文档中的权重进行排序
根据该词在文档中的权重,在调用sort的时候指定降序排序
std::sort(allTokenResult.begin(),allTokenResult.end(),[](const Weight& w1,const Weight& w2)
{
return w1.weight>w2.weight;
}
);
4.构造结果:把最终的结果进行查正排,查到相关内容构造成JSON格式的数据(这里需要用到jion库 jsoncpp),将结果转化为json串传给浏览器客户端。
四.服务器模块
这里我直接下载了个httplib库,直接模拟http服务器,然后自己初始化搜索模块、服务器模块,约定客户端通过query这样的参数来传递查询词,首先需要获取到查询词,然后通过searcher对象来进行搜索
Server sever;
sever.Get("/searcher",[&searcher](const Request& req,Response& resp)
{
//:1.获取到query参数
bool ret=req.has_param("query");
if(!ret)
{
resp.set_content("query parm miss!","text/plan");
return;
}
string query=req.get_param_value("query");
//:2.使用searcher对象来进行搜索
string result;
searcher.search(query,&result);
resp.set_content(result,"application/json");
});
源码地址:https://github.com/GeniusPanYiHao/LINUX/tree/master/Search-Engines/doc_Search