Boost搜索引擎
1. 项目的相关背景
研发搜索引擎的公司,如百度、搜狗、360搜索,还有各大网站各种客户端也提供搜索功能
为什么选择实现Boost搜索引擎
1)因为Boost官方网站是没有搜索功能的,所以我们可以为Boost实现一个站内搜索引擎,虽然官方提供了boost相关的一些方法,标准库中的一些接口,但是我们想看到官方文档成本比较高,所以我们可以自己做一个站内搜索
2)自行实现一个全网搜索引擎难度极大,是十分困难的,但是实现站内搜索,也就是只搜索网站内的内容,这样搜索的内容更垂直(即:搜索的内容有很强的相关性 ),数据量更小,也可以达到以小见大的效果,
对于搜索结果,基本包含三个部分:网页标题,网页内容摘要,目标网页地址
我们还可以发现,有时候我们可以搜到一个广告推销,本质上,广告是搜索引擎的一种盈利的方式,每一个客户在用搜索引擎的时候,都要搜索关键字,所以这些搜索引擎可以出售这些关键字, 谁给的钱越多,谁的搜索结果就越靠前
2.搜索引擎的相关宏观原理
程序跑起来,一定是在内存当中跑
1)还没有进行搜索之前,首先需要在全网当中抓取网页,假设放在data目录下,里面包含了抓取的所有的网页信息
2)把网页抓取下来之后,对内容进行去标签,数据清理, 只保留网页的内容,标题,网页的url
3)searcher建立索引,既然要搜索肯定要建立索引:这个索引作用是为了加速查找
4)开始搜索,服务器一旦启动,浏览器就需要通过http请求的方式进行搜索任务, (本质还是提交http请求,然后在我们的服务端执行搜索任务)
3. 相关技术栈和项目环境
技术栈
- 后端:C/C++,C++11,STL,Boost标准库,Jsoncpp,cppjieba,cpp-httplib
- jsoncpp:对响应的内容完成序列化的操作
- cppjieba:对搜索关键字分词,组合成各种搜索的关键字然后进行文档搜索
- cpp-httplib:开源库,构建http服务器
- 前端:html5,jQuery,Ajax
项目环境
Centos7云服务器,vim/gcc(g++)/Makefile,vscode
4.搜索引擎具体原理-正排索引 && 倒排索引
搜索引擎必然要对内容建立索引,才能更快的搜索和返回,有两种索引:正排索引和倒排索引,以如下内容举例:
有如下两个文档,我们对这两个文档内容建立索引:
文档1:雷军买了四斤小米 文档2:雷军发布了小米手机
正排索引
建立正排索引本质就是建立文档ID和文档内容的对应关系,正排索引就是根据文档ID找到文档内容
文档ID | 文档内容 |
---|---|
1 | 雷军买了四斤小米 |
2 | 雷军发布了小米手机 |
对文档分词
拿到文档首先要对其编号,其次对文档内容进行分词,也就是得到文档内的关键字,为的是建立倒排索引和方便查找
- 对于文档1:雷军买了四斤小米 可以分词为:雷军/买/了/四斤/小米/四斤小米
- 对于文档2:雷军发布了小米手机 可以分词为:雷军/发布/了/小米/小米手机
其中对于“了”、“呢”、“的”、“啊”,这些词都被称为停止词或暂停词,这些词对我们建立索引是没有意义的,一般我们在分词的时候可以不考虑,因为这些词的出现频率太高了,如果保留下来,搜索的时候区分唯一性的价值不大,会增加建立索引的成本,乃至于增加搜索的成本
倒排索引
倒排索引就是根据文档内容,进行分词,整理具有唯一性不重复的关键字,再根据关键字找到关联文档ID的方案,简单来说就是:根据关键字,找到其在哪些文档ID出现过
关键字(具有唯一性) | 文档ID(在哪些文档出现过) |
---|---|
雷军 | 1,2 |
买 | 1 |
四斤 | 1 |
小米 | 1,2 |
四斤小米 | 1 |
发布 | 2 |
小米手机 | 2 |
模拟一次搜索的过程:
用户输入:小米 -> 在倒排索引中查找该关键字,提取出文档ID (1, 2) -> 根据文档ID查出正排索引,找到文档内容 ->获取文档的标题、内容、描述、URL -> 对文档结果进行摘要 -> 构建响应并返回
- 可能一个关键字出现在多个文档当中,所以我们可以根据谁的权重更高,就把谁的文档放在前面展示
5.数据去标签与数据清洗的模块
获取数据源:
boost官网: https://www.boost.org/
1.下载搜索的数据源: https://www.boost.org/users/history/version_1_78_0.html
2.通过拖拽/re -E
指令,把压缩包上传到云服务器当中
3.然后进行解包: tar xzf 压缩包名字
实际上我们查的大部分的手册,都是在doc目录下的html
这个就是标准库对应的各种boost组件对应的手册内容,就是一个个的网页信息 ,就是我们的数据源
创建一个名字为data的目录,里面包含一个input目录,input里面放我们的数据源, 把上述的数据源拷贝到input里面
目前我们只需要boost_1_78_0/doc/html目录下的html文件,用它来进行建立索引
认识标签和去标签
现在我们首先将数据源中的各个文档去标签化,HTML 是标签化语言,所有的语句都被一对标签包裹起来,由左右尖括号括起来的就是标签,对数据本身是无意义的,所以我们首先要将其去掉
- 一般标签都是成对出现的,标签中的属性信息也是不需要的,只有标签内的数据是有用数据
<!--例子-->
<title>Chapter 37. Boost.STLInterfaces</title>
<link rel="stylesheet" href="../../doc/src/boostbook.css" type="text/css">
<td align="center"><a href="../../index.html">Home</a></td>
我们可以创建一个目录存放清洗干净的html文档
其中:input当中放的是:原始的html文档,也就是数据源 raw_html/raw.txt当中放的是:去标签之后的干净文档内容
我们可以看一下当前有多少个html数据源:
我们当前的目标就是:把每个文档都进行去标签,把清洗后的内容写到同一个文件中, 写入到文件当中,一定要考虑下一次在读取的时候也要方便操作
- 我们选择的方案是: html文件内部:以\3分割标题,内容,链接 html文档和html文档之间以\n区分
类似:title\3content\3url \n title\3content\3url \n title\3content\3url \n ...
这样我们使用getline(ifsream, line)
,一次读取一行内容,相当于直接获取一个html文档经过清洗之后的全部内容:title\3content\3url
注意:这里只需要使用两个\3即可,不需要使用三个\3:title\3content\3url\3 \n,因为两个分隔符就可以分隔三个东西
问题:为什么使用\3作为分隔符
首先我们要知道,控制字符是不可以显示的,即下述绿色框框中的(其ascii码值在0~31,127)的字符,而有一些字符是打印字符,现在的我们的文档内容是属于打印字符的范畴
我们使用\3作为分隔符,因为\3是控制字符是控制字符,不显示,不会污染我们的文档内容,当然,我们也可以使用\4之类的不可显字符作为分割字符
注意: \3,在文档当中是以^c 即出现
关于参数的说明
const & 表示是输入型参数
& 表示是输入输出型参数
* 表示是输出型参数
编写parser.cc
基本框架
主要过程:
1.获取所有的带路径的文件名数据源html,进行解析,解析成:title,content,url的形式
2.把解析后的数据放到清洗后数据存放位置
//首先我们涉及到读取文件的动作,我们先把将要读取的文件的路径定义出来,方便我们进行读取
const std::string src_path = "data/input"; // html网页数据源路径,input下面放的是所有的html网页
const std::string output = "data/raw_html/raw.txt"; // 文档数据清洗之后的保存路径
// 解析html文档,每一个html文档都被拆成下面的样子:
typedef struct DocInfo_t
{
std::string title; // 文档标题
std::string content; // 文档内容
std::string url; // 该文档在官网中的url
}DocInfo_t;
//先声明
bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list);
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results);
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output);
int main()
{
std::vector<std::string> files_list; //保存所有的带路径的html文件
//第一步:递归式的把src_path路径下的每个html文件名带路径,保存到files_list中
//为什么这样做? 方便后期进行一个一个的文件进行读取
if(!EnumFile(src_path, &files_list)) //枚举所有带路径的文件名
{
std::cerr << "enum file name error!" << std::endl;
return 1;
}
//第二步:文件内容的解析
std::vector<DocInfo_t> results;//存放每个文档解析完的内容-DocInfo_t结构
if(!ParseHtml(files_list, &results)){
//本质是读取files_list路径的文件内容并且解析
std::cerr << "parse html error" << std::endl;
return 2;
}
//第三步: 把解析完毕的各个文件内容写入到output路径对应的文件
if(!SaveHtml(results, output)){
std::cerr << "sava html error" << std::endl;
return 3;
}
return 0;
}
枚举带路径的html文件
C++对文件系统的支持并不是很好,所以我们需要使用的是boost库的file system模块
#include <boost/filesystem.hpp> //引入boost库的file system模块
首先,我们需要在centos下安装在boost库 sudo yum install -y boost-devel
- 这里需要注意的是:
我们进行搜索的是1.78版本的官方手册, 而我们用的boost库的版本是1.53 来写代码,这个是两码事
不建议直接将命名空间展开,防止命名冲突 ,减少冲突概率
第一步: 使用boost::filesystem命名空间下的path类型定义一个路径对象,并使用我们的参数路径进行初始化,需要判断当前路径释放是存在的
第二步:定义一个空迭代器,然后遍历该路径的文件,如果该文件不是普通文件就不处理,如果是普通文件还要判断后缀是不是以html结尾,
//第一个参数:所有文件保存的路径 第二个参数:输出型参数,枚举的文件名保存的位置
bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list)
{
namespace fs = boost::filesystem;
fs::path root_path(src_path);//定义一个path类型的对象,遍历的时候就从这个路径下开始
//判断root_path这个路径是否存在,如果不存在,就没有必要再往后走了
if(!fs::exists(root_path))
{
//注意:这里不能直接打印root_path,因为它是用boost库的filesystem定义的对象,是一个对象!!
std::cerr << src_path << " not exists" << std::endl;
return false;
}
//对文件递归遍历,定义一个空的迭代器,用来进行判断递归结束
fs::recursive_directory_iterator end;//迭代器是模拟指针的行为,这个空迭代器可以认为是nullptr
//从root_path开始遍历 iter也是迭代器对象,用root_path构造它
for(fs::recursive_directory_iterator iter(root_path); iter != end; iter++)
{
//对文件进行筛选,判断文件是否是普通文件:html都是普通文件
if(!fs::is_regular_file(*iter))
{
continue;//不是普通文件就不处理
}
//这个普通文件还必须以html结尾,判断文件后缀
//iter->path():返回当前迭代器所在的路径 extension方法:提取路径的后缀
if(iter->path().extension() != ".html"){
//判断 文件路径名的后缀是否符合要求
continue;
}
//测试: 来到这里,说明当前的路径一定是一个合法的,以.html结束的普通网页文件
//std::cout << "debug: " << iter->path().string() << std::endl;
//iter->path()得到的还是路径对象, string():将对象所对应的路径以字符串的形式呈现出来
files_list->push_back(iter->path().string()); //将所有带路径的html保存在files_list,方便后续进行文本分析
}
return true;
}
解析html文件
大致框架
先读取文件的所有内容,再依次解析文件的 title、content、url,解析成功后拷贝至解析结果数组中
//第一个参数:保存所有带路径的html文件 第二个参数:输出型参数,解析结果数组
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results)
{
//遍历所有html文件路径
for(const std::string &file : files_list)
{
//1. 读取文件,file是带路径的html文件,打开它然后把它的内容全部读取出来
std::string result; //把读取到的内容放到result当中
if(!ns_util::FileUtil::ReadFile(file, &result)){
continue;//读取失败,说明文件打开失败,不管它
}
//此时result当中保存的就是网页的内容
DocInfo_t doc;
//2. 解析指定的文件,提取title
if(!ParseTitle(result, &doc.title)){
continue;
}
//3. 解析指定的文件,提取content,本质就是去标签,只保留网页的内容
if(!ParseContent(result, &doc.content)){
continue;
}
//4. 解析指定的文件路径,构建url
if(!ParseUrl(file, &doc.url)){
continue;
}
//来到这里,一定是完成了解析任务,当前文档的相关内容:title,content,url都保存在了doc结构体里面
//results->push_back(doc) //细节:push_back,本质会发生拷贝,效率可能会比较低,所以直接移动
results->push_back(std::move(doc)); //doc是临时对象,相当于是资源转移
}
return true;
}
注意:上述提取title和content, 传入的第一个参数是当前获得的html文件的内容,而构建ur传入的第一个参数则是当前html文件的路径,下面详解!
读取html文件
我们可以准备一个Util.hpp文件,存放我们的工具类,并且把内容都放在ns_util命名空间下面,所以我们可以单独把读取文件的函数写在一个类里面:
我们可以直接使用ifstream流进行读取,使用getline一次读取一行内容,放在out里面,最后记得关闭文件流!!!
class FileUtil
{
public:
//第一个参数:该文件的路径 第二个参数:输出型参数,读到的文件内容返回出去
static bool ReadFile(const std::string &file_path, std::string *out)
{
std::ifstream in(file_path, std::ios::in); //in表示读取
if(!in.is_open()) //判断是否打开成功
{
std::cerr << "open file " << file_path << " error" << std::endl;
return false;
}
//文件读取
std::string line; //line保存的就是读取到的一行内容
while(std::getline(in, line))
{
*out += line;
}
in.close(); //关闭文件
return true;
}
};
问题:如何理解getline读取到文件结束呢?
getline的返回值是一个流的引用,while循环判断是一个bool类型 ,本质是因为返回的对象当中重载了强制类型转化,意思就是判断对象的真假,这个对象里面就做了重载,重载强转之后得到的就是bool值
下面三个:提取title,content,构建url的三个函数都设置为static静态函数,我们只想在本源文件中有效
提取title
一般在html网页里面只有一对title,我们要做的就说提取这对标签里面的内容
其实很简单,只需要找到<title>
和</title>
位置即可, begin指向第一个<位置,end指向第二个<位置,然后begin跳过<title>
个长度,来到标题的正文位置, 此时[begin,end)就是标题的内容
//第一个参数:file里面就是网页内容 第二个参数:输出型参数,存放当前网页的标签内容
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移动到标题内容的起始位置
begin += std::string("<title>").size();
if(begin > end){
return false;
}
*title = file.substr(begin, end - begin);//[begin,end)
return true;
}
提取content
本质是进行去标签,在遍历整个html文件内容的时候,只要碰到>
就意味着当前标签被处理完毕,只要碰到<
就意味着即将处理新标签, 所以可以用枚举类型描述这2种状态,条件就绪更改状态,遇到内容时就插入到对应字符串中
//去标签(数据清洗)操作
static bool ParseContent(const std::string &file, std::string *content)
{
//基于一个简易的状态机
enum status{
//枚举两种状态
LABLE,//正在读的是标签里面的内容
CONTENT//现在读取的是正常文本内容
};
enum status s = LABLE;//刚开始一定是标签
//file:就是整个html文件的内容(网页的内容), 按字符读取
for( char c : file)//这里没有加引用!!!
{
switch(s)
{
//此时是标签状态,说明该字符可能是标签的一部分,如果是,则忽略,继续下次循环读取
//但是需要注意:如果当前字符是右标签,说明下一次就是正常的文本内容
case LABLE:
if(c == '>') s = CONTENT; //碰到右标签 ->标签状态结束
break;
//当前是文本状态,说明该字符可能是文本的一部分,如果是则插入到输出型参数当中
//但是需要注意:如果当前字符是左标签,说明下一次就是读取标签的内容
case CONTENT:
if(c == '<')
s = LABLE;//碰到左标签 ->内容读取完了,变换状态
else //意味着此时是内容的状态,并且不是左标签
{
//小细节:我们不想保留原始文件中的\n,因为我们想用\n作为html解析之后文本的分隔符
//所以我们把\n换成空格!所以这也是为什么不加引用的原因!否则就对源html文件做修改了
if(c == '\n') c = ' ';
content->push_back(c);//插入当前字符
}
break;
default:
break;
}
}
return true;
}
构建url
需要注意:boost库的官方文档,和我们下载下来的文档的路径,是有对应关系的
官网URL样例: https://www.boost.org/doc/libs/1_78_0/doc/html/accumulators.html
我们下载下来的url样例: boost_1_78_0/doc/html/accumulators.html
我们拷贝到我们项目中的样例: data/input/accumulators.html
所以:我们只需要构造url的头部为:url_head = "https://www.boost.org/doc/libs/1_78_0/doc/html"
然后url的尾部为: url_tail = /accumulators.html
即可,也就是把我们项目当中的样例前面的 data/input
删掉
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_78_0/doc/html";//前缀
//最初我们最开始已经定义了路径src_path = "data/input" 放的是所有的html数据源
//file_path就相当于:data/input/accumulators.html 然后把前面的内容data/input去掉
std::string url_tail = file_path.substr(src_path.size());
//构成官网链接
*url = url_head + url_tail;
return true;
}
写法2: 不使用src_path
bool ParserUrl(const std::string& file, std::string* url)
{
std::string url_head = "https://www.boost.org/doc/libs/1_80_0/doc/html/"; // 构建前缀,注意这里html后面加了个/
int begin = file.rfind('/');//注意是从后往前找/
if (begin == std::string::npos) {
std::cout << "file suffix find error" << std::endl;
return false;
}
//[begin+1,file的结束位置]就是后缀
std::string url_tail(file, begin + 1);
*url = url_head + url_tail;
return true;
}
数据保存
把解析完毕的各个文件内容写入到output路径对应的文件, 以\3为文档内容之间的区分, 文档与文档之间用\n区分
title\3content\3url \n title\3content\3url \n title\3content\3url \n
这样我们使用getline
一次读取一行能够直接获得一个html文档的属性信息
//第一个参数:经过清洗之后的html文件属性信息数组 第二个参数:保存解析完毕的文件内容
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{
#define SEP '\3' //分隔符
//这里按照二进制方式进行写入,但是用文本写入也是可以的,但是这里因为有\3
//二进制写的特点是:写入的是什么,文档里保存的就是什么,程序不会给我们做自动转化
std::ofstream out(output, std::ios::out | std::ios::binary);//因为是要输出->所以是out,以二进制形式写
if(!out.is_open()){
//打开文件失败
std::cerr << "open " << output << " failed!" << std::endl;
return false;
}
//遍历数组
for(auto &item : results) //item就是DocInfo_t结构,放的是一个html网页的标题,内容,链接
{
//文档内的分隔符是\3 文档间的分割方是\n
std::string out_string;
out_string = item.title;//标题
out_string += SEP;// \3
out_string += item.content;//内容
out_string += SEP;// \3
out_string += item.url; //链接
out_string += '\n'; //一个文档和一个文档的分割符是\n
//第一个参数:要写入的数据, 第二个参数:写入的字节数
out.write(out_string.c_str(), out_string.size());//把字符串的内容写到文件
/*
std::string out_string(item._title + SEP + item._content + SEP + item._url + '\n');
out.write(out_string.c_str(), out_string.size());
*/
}
out.close();//记得关闭流!
return true;
}
用于debug函数
//for debug
static void ShowDoc( const DocInfo_t &doc)
{
std::cout << "title: " << doc.title << std::endl;
std::cout << "content: " << doc.content << std::endl;
std::cout << "url: " << doc.url << std::endl;
}
可以在解析完html文件之后,调用该函数调试!由于内容过多,所以可以选择输出一个之后就break退出循环
运行结果:
我们之前看到的是,data/input目录下一共有8141个html文件, 现在处理之后一共有8141行,符合我们的预期!raw.txt文件内部的每一行都是一个网页对应的内容:标题\3内容\3网页链接 \n
\3对应的值就是^c ,属性之间的分隔符是\3
显示为^C
,文档之间的分隔符是\n
不显示,并且我们可以赋值网址到浏览器搜索,看是否正确
可以直接 cat raw.txt | head -1
//查看前面的几行/后面的几行进行验证
如果url出现问题,调试,输出结果看是否符合预期,
例如:可能是src_path的路径为:data/input/
导致的错误,因为我们的url_head最后面的html是没有加/的 此时我们对data/input/accumulators.html截取尾部获得的就是accumulators.html,所以拼接起来就是:https://www.boost.org/doc/libs/1_78_0/doc/htmlaccumulators.html
解决办法: src_path后面的路径不加/ ,直接就是data/input ,或者url_head的html后面加个/,也就是https://www.boost.org/doc/libs/1_78_0/doc/html/文章来源:https://www.toymoban.com/news/detail-438571.html
文章来源地址https://www.toymoban.com/news/detail-438571.html
汇总:parser.cc
#include <iostream>
#include <string>
#include <vector>
#include <boost/filesystem.hpp> //引入boost库
#include "util.hpp"
//首先我们涉及到读取文件的动作,我们先把将要读取的文件的路径定义出来,方便我们进行读取
const std::string src_path = "data/input"; // html网页数据源路径,input下面放的是所有的html网页
const std::string output = "data/raw_html/raw.txt"; // 文档数据清洗之后的保存路径
// 解析html文档,每一个html文档都被拆成下面的样子:
typedef struct DocInfo_t
{
std::string title; // 文档标题
std::string content; // 文档内容
std::string url; // 该文档在官网中的url
}DocInfo_t;
//先声明
bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list);
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results);
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output);
int main()
{
std::vector<std::string> files_list; //保存所有的带路径的html文件
//第一步:递归式的把src_path路径下的每个html文件名带路径,保存到files_list中
//为什么这样做? 方便后期进行一个一个的文件进行读取
if(!EnumFile(src_path, &files_list)) //枚举所有带路径的文件名
{
std::cerr << "enum file name error!" << std::endl;
return 1;
}
//第二步:文件内容的解析
std::vector<DocInfo_t> results;//存放每个文档解析完的内容-DocInfo_t结构
if(!ParseHtml(files_list, &results)){
//本质是读取files_list路径的文件内容并且解析
std::cerr << "parse html error" << std::endl;
return 2;
}
//第三步: 把解析完毕的各个文件内容写入到output路径对应的文件
if(!SaveHtml(results, output)){
std::cerr << "sava html error" << std::endl;
return 3;
}
return 0;
}
//第一个参数:所有文件保存的路径 第二个参数:输出型参数,枚举的文件名保存的位置
bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list)
{
namespace fs = boost::filesystem;
fs::path root_path(src_path);//定义一个path类型的对象,遍历的时候就从这个路径下开始
//判断root_path这个路径是否存在,如果不存在,就没有必要再往后走了
if(!fs::exists(root_path))
{
//注意:这里不能直接打印root_path,因为它是用boost库的filesystem定义的对象,是一个对象!!
std::cerr << src_path << " not exists" << std::endl;
return false;
}
//对文件递归遍历,定义一个空的迭代器,用来进行判断递归结束
fs::recursive_directory_iterator end;//迭代器是模拟指针的行为,这个空迭代器可以认为是nullptr
//从root_path开始遍历 iter也是迭代器对象,用root_path构造它
for(fs::recursive_directory_iterator iter(root_path); iter != end; iter++)
{
//对文件进行筛选,判断文件是否是普通文件:html都是普通文件
if(!fs::is_regular_file(*iter))
{
continue;//不是普通文件就不处理
}
//这个普通文件还必须以html结尾,判断文件后缀
//iter->path():返回当前迭代器所在的路径 extension方法:提取路径的后缀
if(iter->path().extension() != ".html"){
//判断 文件路径名的后缀是否符合要求
continue;
}
//测试: 来到这里,说明当前的路径一定是一个合法的,以.html结束的普通网页文件
//std::cout << "debug: " << iter->path().string() << std::endl;
//iter->path()得到的还是路径对象, string():将对象所对应的路径以字符串的形式呈现出来
files_list->push_back(iter->path().string()); //将所有带路径的html保存在files_list,方便后续进行文本分析
}
return true;
}
//第一个参数:经过清洗之后的html文件属性信息数组 第二个参数:保存解析完毕的文件内容
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{
#define SEP '\3' //分隔符
//这里按照二进制方式进行写入,但是用文本写入也是可以的,但是这里因为有\3
//二进制写的特点是:写入的是什么,文档里保存的就是什么,程序不会给我们做自动转化
std::ofstream out(output, std::ios::out | std::ios::binary);//因为是要输出->所以是out,以二进制形式写
if(!out.is_open()){
//打开文件失败
std::cerr << "open " << output <
到了这里,关于【项目】Boost搜索引擎的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!