C++之基于正倒排索引的Boost搜索引擎项目数据清洗代码及详解(下)
1. 第一步详解
我们要使用到boost里面filesystem这个命名空间里面的函数,所以我在这里先给它取个别名。然后我们把src_path里面的路径交给root_path。接着我们判断这个路径是否存在,如果不存在那就直接结束代码。接着我们通过迭代器循环的方式来对root_path里面的每一个文件。
第一个if用来判断是否是普通文件,第二个if来判断文件的扩展名是否为 .html,接着走到最后就书面是扩展名为 .html的普通文件。然后我们就把它的路径转化为string类型。
注意:在这里不可以把.string()换成to_string()。这是因为:
.string()是std::filesystem::path类的成员函数,专门用于将路径对象转换为std::string类型的字符串(返回路径的字符串表示)。to_string()是 C++ 标准库中的全局函数(或针对基础类型的重载),用于将数值类型(如int、double等)转换为字符串,不能直接用于路径对象。
bool EnumFile(const std::string &src_path,std::vector<std::string> *file_list) { namespace fs=boost::filesystem;//可以理解为对filesystem区别名 fs::path root_path(src_path);//把src_path文件的路径交给root_path if(!fs::exists(root_path))//判断路径是否存在,如果不存在,那就结束 { std::cout<<src_path<<"is not exist"<<std::endl; return false; } fs::recursive_directory_iterator end;//recursive_directory_iterator是一个递归迭代器,现在这个end是指向空 for(fs::recursive_directory_iterator iter(root_path); iter!=end; iter++)//创建一个iter迭代器,如果=end就代表访问完了 { //is_regular_file是用来判断是否是普通文件(html是普通文件),是的话就返回true if(!fs::is_regular_file(*iter)) continue;//走到这里说明不是普通文件,那就跳过 //这行代码的作用是判断当前遍历到的文件的扩展名是否为 .html,只有扩展名是 .html 的文件才会被处理(加入到 files_list 中 ) if(iter->path().extension()!=".html") continue; //代码走到这里说明是以.html结尾的普通网页文件 file_list->push_back(iter->path().string());//.string()的意思就是把iter->path()的内容转化成string类型 } return 0; }2. 第二步详解
2.1读取并解析(去标签化)
这个就是去标签化的函数,通过一步步对文件的解析分别提出title和content,然后构建出url,接下来就是把doc里面的内容交给results。
这是分开处理是因为写在一起会显得代码很多,不好找错误。
bool ParseHtml(const std::vector<std::string> &files_list,std::vector<DocInfo_t> *results) { for(const std::string &file:files_list) { //1. 读取文件,Read() std::string tr;//temporary results临时结果 if(!ns_util::FileUtil::ReadFile(file,&tr)); continue; //2. 解析指定的文件,提取title DocInfo doc; if(!ParseTitle(tr,&doc.title)) continue; //3. 解析指定的文件,提取content if(!ParseContent(tr,&doc.content)) continue; //4.解析指定的文件路径,构建url if(!ParseUrl(file,&doc.url)) continue; //走到这里就是已经完成了上面的4个任务,同时文档的标题,内容和url都已经存储到doc里面了 //接下来就是把doc里面的内容交给results results->push_back(doc); } return 0; }2.2 提取title
因为网页的标题一般是<title>XXXXXXXXXXXXXXXXXXX</title>,然后我们实际上只要XXXXXXXXXXXXXXXXXXX这个部分,所以我们通过找位置的方式来确定实际想要的内容的位置,然后把它全部获取。
//这边加上static是因为不想被别的文件使用(其实加不加都大差不差) static bool ParseTitle(std::string &file,std::string *title) { //因为网页的标题一般是<title>XXXXXXXXXXXXXXXXXXX</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);//把标题加入到*title里面 return true; }2.3提取content
我们在这里先创建一个 enum类型的结构体,用来判断是标签还是我们想要的内容。
然后通过迭代器和switch判断的方式,来把所有的<>的部分去掉。如果是在标签内就一直跳过,如果遇到>就判断下一个是不是<,不是的话就代表是我们想要的内容。
PS:在这里我们不用和提取title一样的方式是因为在content里面,有各种各样的标签,而title里面的标签就只有<title>这一种。
static bool ParseContent(std::string &file,std::string *content) { typedef enum status{//创建一个enum类型,用于判断s当前是什么状态 LABLE, CONTENT }judge; judge s=LABLE; //下面这个的逻辑就是遇到<就一直跳过去找>,然后下一个不是<的话就+=到content for(char c:file) { switch(s) { case LABLE: if(c=='>') s==CONTENT; break; case CONTENT: if(c=='<') s==LABLE; else { if(c=='/n') c==' '; *content+=c; } break; default: break; } } return true; }2.4构建url
用于构建完整 URL 的函数,其作用是将本地文件路径转换为对应的在线 URL 地址。(类似于通过本地连接跳转在线)
建立本地文件与在线文档的对应关系。通过将本地文件的相对路径(相对于src_path)拼接到固定的 URL 头后面,实现从本地文件路径到在线文档 URL 的转换。
注释中提到 不写死 的好处是:当需要处理同一基础 URL 下的多个不同页面时,只需提供不同的本地文件路径,就能自动生成对应的在线 URL,无需为每个页面单独编写 URL。
PS:就像拼图一样,固定一个底座(网址开头),再拼上不同的零件(相对路径),就能得到不同的完整图片(完整网址)。
static bool ParseUrl(const std::string &file_path,std::string *url) { std::string url_head="https://www.boost.org/doc/libs/1_89_0/doc/html"; std::string url_tail=file_path.substr(src_path.size()); //std::string url_head="accumulators.html"; //这边没有像注释里面那样写是因为不写死,这样可以访问多个head一样,tail不一样的网页 *url=url_head+url_tail; return true; }3. 第三步详解
先以二进制的方式打开output,接着通过迭代器访问results的方式把处理完的信息一段一段的放入临时创建的out_string里面,接着当收集完一个网页的信息后在放入output里面。
PS:在out.write这里要加上C_str是因为这个write()函数是C语言的函数,不认识C++的string,所以我们要转换成它认识的。
最后关闭out,完成所有的读取。
bool SaveHtml(const std::vector<DocInfo_t> &results,const std::string &output) { //output存结果,这个函数是把results里面的值处理后交给output //std::ofstream out(output,std::ios::out | std::ios::binarry); std::ofstream out(output); //上面这行的意思是以二进制模式打开文件 if(!out.is_open())//判断是否打开成功,如果失败就报错返回 { std::cout<<"open "<<output<<" failed!"<<std::endl; return false; } //通过迭代器把results里面的东西放入临时变量out_string里面,然后 for(auto& item:results) { std::string out_string; out_string+=item.title; out_string+='\3'; out_string+=item.content; out_string+='\3'; out_string+=item.url; out_string+='\n'; out.write(out_string.c_str(),out_string.size()); //这边就是把out_string转换成C语言认识的字符窜类型然后以out_string.size()的大小传入output里面 //out_string也是字符窜类型的,在这里要c_string转换是因为write是C语言的接口不认识C++的string类型,所以要转换成C语言的字符窜 } out.close(); return 0; }