阅读导航
- 一、网页数据下载
- 二、编写数据去标签与数据清洗的模块 Parser
- ✅boost 开发库的安装
- 1. 基本思路
- 2. 详细讲解
- (1)程序递归遍历目录,收集所有HTML文件的路径
- (2)对每个HTML文件进行解析,提取出文档标题、内容和URL
- (3)将这些信息保存到一个文本文件中,每个文档信息之间用特定分隔符隔开
- 三、结果验证
- 1. 编辑makefile文件
- 2. 生成可执行程序
一、网页数据下载
🔴下载网页界面链接
-
打开了上面的网页以后下载
boost_1_84_0.tar.gz
压缩包就可以了。 -
下载以后,打开Linux建立相关目录,在这个目录下输入
rz -E
命令选中刚才我们下载的压缩包,把压缩包传输进Linux机器中。 -
在控制台输入
tar xzf boost_1_84_0.tar.gz
进行解压
-
解压出来上面的这些文件,我们需要的数据是在文件夹
doc/html
里面,我们把它复制到我们创建的data/input
文件夹里面使用cp -rf boost_1_84_0/doc/html/* data/input
-
把刚才解压过的文件删除了就可以了。
我们已经收集到了网页的初始信息,但是在这些信息中存在着一些无用的网页标签和其他数据,需要进行清理处理,以便我们能够更好地分析和利用这些数据。
二、编写数据去标签与数据清洗的模块 Parser
✅boost 开发库的安装
PS:我们要先在Linux机器上安装Boost库:sudo yum install -y boost-devel
1. 基本思路
- 程序递归遍历目录,收集所有HTML文件的路径;
- 对每个HTML文件进行解析,提取出文档标题、内容和URL;
- 将这些信息保存到一个文本文件中,每个文档信息之间用特定分隔符隔开。
✅整个处理流程由main
函数协调,涉及文件操作、字符串解析和错误处理等技术,旨在高效地从HTML文件中提取结构化数据,为后续的数据使用或分析提供便利。
2. 详细讲解
(1)程序递归遍历目录,收集所有HTML文件的路径
- 程序代码
// 遍历目录,获取所有HTML文件的路径
bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list) {
namespace fs = boost::filesystem;
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;
}
// 跳过非HTML文件
if (iter->path().extension() != ".html") {
continue;
}
// 将HTML文件路径添加到列表中
files_list->push_back(iter->path().string());
}
return true;
}
- 代码解释
函数接受一个字符串参数src_path
,这个参数表示需要遍历的目录路径。函数的返回类型是bool
,表示操作的成功与否。函数还接受一个指向std::vector<std::string>
的指针作为参数,这个向量用于存储找到的HTML文件路径。
在函数内部,首先创建了一个boost::filesystem
的path
对象,用于表示传入的源路径。接着,使用fs::exists
函数检查这个路径是否存在。如果路径不存在,函数输出错误信息并返回false
。
如果路径存在,函数使用fs::recursive_directory_iterator
来创建一个迭代器,这个迭代器可以遍历目录及其所有子目录。迭代器的结束状态由end
变量表示。在迭代过程中,函数检查每个文件是否是普通文件(fs::is_regular_file
),如果不是,则跳过。然后,检查文件扩展名是否为.html
,如果不是HTML文件,也跳过。
对于每个符合条件的HTML文件,使用iter->path().string()
获取其完整的路径字符串,并将其添加到传入的files_list
向量中。
(2)对每个HTML文件进行解析,提取出文档标题、内容和URL
- 程序代码
// 辅助函数,用于解析HTML文件中的标题
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(); // 调整起始位置,跳过"<title>"
if(begin > end){ // 如果起始位置在结束位置之后
return false; // 返回失败
}
// 提取标题内容
*title = file.substr(begin, end - begin);
return true; // 返回成功
}
// 辅助函数,用于解析HTML文件中的内容(去除HTML标签)
static bool ParseContent(const std::string &file, std::string *content)
{
//去标签,基于一个简易的状态机
enum status{
LABLE, // 标签状态
CONTENT // 内容状态
};
enum status s = LABLE; // 初始化状态为标签状态
for( char c : 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; // 返回成功
}
// 辅助函数,用于构建文档的URL
static bool ParseUrl(const std::string &file_path, std::string *url)
{
// 构造URL头部
std::string url_head = "https://www.boost.org/doc/libs/1_78_0/doc/html";
// 提取URL尾部
std::string url_tail = file_path.substr(src_path.size());
// 拼接URL
*url = url_head + url_tail;
return true; // 返回成功
}
// 解析HTML文件,提取标题、内容和URL
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results) {
for (const std::string &file : files_list) {
// 读取文件内容
std::string file_content;
if (!ns_util::FileUtil::ReadFile(file, &file_content)) {
continue;
}
// 解析标题
DocInfo_t doc;
if (!ParseTitle(file_content, &doc.title)) {
continue;
}
// 解析内容(去除HTML标签)
if (!ParseContent(file_content, &doc.content)) {
continue;
}
// 解析URL
if (!ParseUrl(file, &doc.url)) {
continue;
}
// 将解析结果添加到结果集中
results->push_back(std::move(doc));
}
return true;
}
- 代码解释
-
ParseTitle
函数:- 这个函数的目的是从一个HTML文件字符串中提取标题(
<title>
标签内的内容)。 - 它首先查找
<title>
标签的开始位置,如果没有找到,返回false
。 - 然后,它查找
</title>
标签的结束位置,如果没有找到,同样返回false
。 - 如果找到了开始和结束位置,函数会计算标题的实际长度,并使用
substr
方法提取标题内容。 - 最后,将提取的标题内容通过指针参数
title
返回,并返回true
表示成功。
- 这个函数的目的是从一个HTML文件字符串中提取标题(
-
ParseContent
函数:- 此函数用于去除HTML文件内容中的所有标签,只保留纯文本。
- 它使用一个简单的状态机来区分标签和内容状态。
- 遍历文件中的每个字符,如果是标签状态(
LABLE
),遇到>
字符则切换到内容状态(CONTENT
)。 - 在内容状态下,遇到
<
字符则切换回标签状态,其他字符(除了换行符\n
,将其替换为空格)则添加到内容字符串中。 - 最终,通过指针参数
content
返回处理后的纯文本内容,并返回true
表示成功。
-
ParseUrl
函数:- 这个函数用于构建一个文档的完整URL。
- 它首先定义了一个URL的基础部分,然后从文件路径中提取特定部分作为URL的尾部。
- 通过拼接基础URL和尾部路径,构建完整的URL字符串。
- 通过指针参数
url
返回构建的URL,并返回true
表示成功。
-
ParseHtml
函数:- 这是一个整合函数,它接收一个包含HTML文件路径的向量,并返回一个包含解析结果的
DocInfo_t
类型的向量。 - 对于每个文件路径,它首先读取文件内容,然后依次调用
ParseTitle
、ParseContent
和ParseUrl
函数来提取标题、内容和URL。 - 如果任何一个步骤失败,它会跳过当前文件并继续处理下一个文件。
- 最后,将所有解析成功的文档信息添加到结果集中,并返回
true
表示解析过程完成。
- 这是一个整合函数,它接收一个包含HTML文件路径的向量,并返回一个包含解析结果的
🍁这些函数共同工作,提供了一个从HTML文件中提取有用信息的解决方案。它们可以用于构建搜索引擎索引、内容摘要或其他需要从HTML中提取数据的场景。代码结构清晰,通过模块化的设计提高了可读性和可维护性。
(3)将这些信息保存到一个文本文件中,每个文档信息之间用特定分隔符隔开
- 程序代码
// 保存解析结果到文件
#define SEP '\3' // 定义分隔符
bool SaveHtml(const std::vector<DocInfo_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;
}
// 遍历结果集,将每个文档的信息写入文件
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;
}
- 代码解释
-
定义分隔符:
- 使用预处理器指令
#define
定义了一个名为SEP
的宏,其值为\3
。这个宏用于在保存到文件的文档信息之间创建一个分隔符,以便在后续读取文件时能够区分不同的文档记录。
- 使用预处理器指令
-
打开文件:
- 使用
std::ofstream
创建一个输出文件流out
,尝试以二进制模式打开指定的输出文件。这种模式可以确保写入的数据不会被转换或解释为文本文件中的字符,而是以原始字节形式保存。
- 使用
-
检查文件是否成功打开:
- 通过调用
out.is_open()
检查文件是否成功打开。如果文件没有成功打开,则输出错误信息到标准错误流std::cerr
,并返回false
。
- 通过调用
-
写入文档信息:
- 如果文件成功打开,函数遍历
results
向量中的每个DocInfo_t
结构体。 - 对于每个结构体,创建一个字符串
out_string
,并将结构体中的title
、content
和url
字段依次拼接,每个字段后跟一个定义好的分隔符SEP
,并在每条记录的末尾添加换行符\n
。 - 使用
out.write()
函数将out_string
的内容写入到文件中。out.write()
接受两个参数:指向要写入数据的指针和要写入的字节数。
- 如果文件成功打开,函数遍历
-
关闭文件:
- 在所有文档信息都写入文件后,调用
out.close()
关闭文件。这是一个好的编程实践,可以确保所有的数据都已经被刷新到磁盘,并且释放与文件相关的资源。
- 在所有文档信息都写入文件后,调用
整体而言,SaveHtml
函数负责将解析后的文档信息以一种结构化的方式保存到文件中,以便后续的处理或分析。通过使用二进制模式和特定的分隔符,该函数确保了数据的完整性和可读性。
三、结果验证
完成了上述代码后,我们的基本架构已经基本完成。接下来,我们需要编写一个名为makefile
的文件,以便进行编译和验证结果。
1. 编辑makefile文件
Parser=parser
.PHONY:all
all:$(Parser)
$(Parser):parser.cpp
$(cpp) -o $@ $^ -lboost_system -lboost_filesystem -std=c++11
.PHONY:clean
clean:
rm -f $(Parser)
2. 生成可执行程序
- 在控制台输入:
make
指令,会生成一个名字为parser
的可执行程序
- 输入:
./parser
命令,执行可执行程序 - 打开文件
data/raw_html/raw.txt
就可以看到数据处理后的结果了,如下图(PS:/3
在ASCII码表中表示^c
)