两百万年前,一群穴居人发现,把石头绑在棍子上或许挺管用。从那时起,事情就稍微有点失控了。如今,我们面临的问题已不再是逃离剑齿虎,而是当队友把提交命名为“did sutff”时该怎么办。
尽管困难的性质发生了变化,但我们对创造工具以克服困难的执着却从未改变。网络爬虫便是明证。它们是专为解决数字问题而设计的数字工具。
今天我们将从零开始构建一个新工具,不过这次不用石头和木棍,而是使用C++——这恐怕更难。所以,请继续关注,学习如何制作C++网页爬虫以及如何使用它!

两百万年前,一群穴居人发现,把石头绑在棍子上或许挺管用。从那时起,事情就稍微有点失控了。如今,我们面临的问题已不再是逃离剑齿虎,而是当队友把提交命名为“did sutff”时该怎么办。
尽管困难的性质发生了变化,但我们对创造工具以克服困难的执着却从未改变。网络爬虫便是明证。它们是专为解决数字问题而设计的数字工具。
今天我们将从零开始构建一个新工具,不过这次不用石头和木棍,而是使用C++——这恐怕更难。所以,请继续关注,学习如何制作C++网页爬虫以及如何使用它!
无论你选择哪种编程语言,都必须理解网页抓取工具的工作原理。这一知识对于编写脚本并最终获得一个可运行的工具至关重要。
不难想象,让机器人代劳所有调研工作,远比手动复制信息高效得多。若需处理海量数据,其优势将呈指数级增长。例如,在训练机器学习算法时,网络爬虫能为你节省数月的工作时间。
为了更清晰地说明,让我们逐一梳理网页爬虫能为您带来的帮助:
WebScrapingAPI 的存在正是为了为您带来所有这些优势,同时帮助您绕过爬虫在网络上经常遇到的各种障碍。这一切听起来很棒,但如何将其应用到实际项目中呢?
网络爬虫的应用方式多种多样,可能性无穷无尽!不过,某些用例确实比其他更常见。
让我们详细探讨每种情况:
类似的例子不胜枚举。当然,您也可以开发出全新的数据提取应用方式,从而为己所用。我们始终鼓励用户对我们的API进行创新性的运用。
尽管网络爬虫价值非凡,但并非一切都一帆风顺。有些网站并不欢迎机器人访问。虽然编程让爬虫避免给目标网站造成严重负担并不难,但网站对机器人的警惕也是有道理的。毕竟,网站可能无法区分一个彬彬有礼的机器人和一个恶意的机器人。
因此,爬虫在实际运行中会遇到一些挑战。
其中最棘手的障碍包括:
若要构建网络爬虫,您应了解互联网运作的几个关键概念。毕竟,您的机器人不仅要利用互联网收集数据,还必须融入其他数十亿用户之中。
我们常将网站比作书籍,认为“阅读某一页”仅需翻到相应页码即可。但实际上,这更像是你与书籍之间往来的电报。你请求一页内容,它便被发送给你;接着你请求下一页,同样的过程再次发生。
这一切的往来都通过 HTTP(超文本传输协议)实现。作为访问者的你,要查看一个页面,你的浏览器会向服务器发送一个 HTTP 请求,并收到来自服务器的 HTTP 响应。你想要的内容将包含在该响应中。
HTTP请求由四个组成部分构成:
发送请求后,即使请求未能获取任何数据,你仍会收到响应。其结构如下:
REST API 采用 HTTP 协议,这与网页浏览器类似。因此,如果您认为 C++ 网页爬虫过于繁琐,而选择使用 WebScrapingAPI(该工具已处理所有数据提取难题),这些信息对您同样大有裨益。
在编程语言领域,C 和 C++ 堪称最经典的代表。这种语言诞生于 1985 年,是“带类的 C 语言”的完善版本。
虽然 C++ 适用于通用编程,但它并非开发网络爬虫时首先会考虑的语言。它虽有某些缺点,但这种思路并非毫无可取之处。让我们一起来探讨一下吧?
C++ 是一种面向对象的语言,这意味着它通过类、数据抽象和继承,使您的代码更易于复用并适应不同需求。由于数据被视为对象,您存储和解析数据会更加轻松。
许多开发者至少掌握一点 C++。即使你在大学里没学过,你(或你的队友)很可能也懂一点 C++。这种情况在整个软件开发社区都很普遍,因此你很容易就代码获得第二意见。
C++ 具有极高的可扩展性。如果您从小型项目起步,并决定将网络爬虫作为主要方向,大部分代码都是可复用的。只需在此处彼处稍作调整,您就能应对更大的数据量。
另一方面,C++ 是一种静态编程语言。虽然这能确保更好的数据完整性,但在处理互联网相关事务时,它不如动态语言灵活。
此外,C++ 并不适合构建爬虫。如果你仅需一个数据抓取工具,这可能不是问题。但如果你打算添加爬虫来生成 URL 列表,C++ 则不是一个理想的选择。
好,我们已经讨论了许多重要内容,现在让我们进入本文的核心——用 C++ 编写网页抓取工具。
1. 下载并安装 Visual Studio 后,使用“控制台应用程序”模板创建一个简单项目。
2. 接下来我们将配置包管理器。Vcpkg 提供了一份编写精良的教程,助您快速入门。
注意:安装完成后,建议设置环境变量,以便您可以在计算机上的任何位置运行 vcpkg。
3. 现在是安装所需库的时候了。如果您已设置环境变量,请打开任意终端并运行以下命令:
> vcpkg install cpr
> vcpkg install gumbo> vcpkg integrate install
若未设置环境变量,只需导航至安装 vcpkg 的文件夹,打开终端窗口并运行相同的命令。
前两个命令用于安装构建爬虫所需的包,第三个命令则能帮助我们轻松地将这些库集成到项目中。
现在一切就绪!在构建网页抓取工具之前,我们需要选择一个网站并检查其 HTML 代码。
我们访问了维基百科,并从“你知道吗……”栏目中随机选取了一页。因此,今天要抓取的页面将是关于“罂粟籽防御”的维基百科条目,我们将从中提取部分内容。但首先,让我们看看页面的结构。在文章任意位置右键点击,然后选择“检查元素”,瞧!HTML 代码就呈现在眼前了。
现在我们可以开始编写代码了。要提取信息,我们需要将 HTML 下载到本地。首先,导入我们刚刚下载的库:
#include <iostream>
#include <fstream>
#include "cpr/cpr.h"#include "gumbo.h"
接着,我们向目标网站发起HTTP请求以获取HTML。
std::string extract_html_page()
{
cpr::Url url = cpr::Url{"https://en.wikipedia.org/wiki/Poppy_seed_defence"};
cpr::Response response = cpr::Get(url);
return response.text;
}
int main()
{
std::string page_content = extract_html_page();
}现在 `page_content` 变量中存有文章的 HTML 内容,我们将进一步利用它来提取所需数据。此时 gumbo 库就派上用场了。
我们使用 gumbo_parse 方法将之前的 page_content 字符串转换为 HTML 树,然后对根节点调用我们实现的 search_for_title 函数。
int main()
{
std::string page_content = extract_html_page();
GumboOutput* parsed_response = gumbo_parse(page_content.c_str());
search_for_title(parsed_response->root);
// free the allocated memory
gumbo_destroy_output(&kGumboDefaultOptions, parsed_response);
}被调用的函数将通过递归调用,对 HTML 树进行深度遍历以查找 <h1> 标签。当找到标题时,它会在控制台显示该标题并退出执行。
void search_for_title(GumboNode* node)
{
if (node->type != GUMBO_NODE_ELEMENT)
return;
if (node->v.element.tag == GUMBO_TAG_H1)
{
GumboNode* title_text = static_cast<GumboNode*>(node->v.element.children.data[0]);
std::cout << title_text->v.text.text << "\n";
return;
}
GumboVector* children = &node->v.element.children;
for (unsigned int i = 0; i < children->length; i++)
search_for_title(static_cast<GumboNode*>(children->data[i]));
}对于其余标签,同样遵循这一核心原则:遍历树结构并获取目标内容。现在让我们获取所有链接并提取其 href 属性。
void search_for_links(GumboNode* node)
{
if (node->type != GUMBO_NODE_ELEMENT)
return;
if (node->v.element.tag == GUMBO_TAG_A)
{
GumboAttribute* href = gumbo_get_attribute(&node->v.element.attributes, "href");
if (href)
std::cout << href->value << "\n";
}
GumboVector* children = &node->v.element.children;
for (unsigned int i = 0; i < children->length; i++)
{
search_for_links(static_cast<GumboNode*>(children->data[i]));
}
}看到了吗?除了要查找的标签不同外,代码几乎完全相同。gumbo_get_attribute 方法可以提取我们指定的任何属性。因此,您可以使用它来查找类名、ID 等。
在显示属性值之前,必须对其进行空值检查。在高级编程语言中,这通常是多余的,因为系统会自动显示空字符串;但在 C++ 中,程序会因此崩溃。
文中散布着许多链接,内容交织在一起。而且这篇文章本身也很短。所以让我们把它们全部保存到外部,看看如何区分它们。
首先,我们创建并打开一个 CSV 文件。这一步应在函数外部进行,紧邻导入语句处。由于我们的函数是递归的,如果每次调用都创建新文件,将会产生大量文件。
std::ofstream writeCsv("links.csv");
接着,在主函数中,我们在首次调用该函数之前写入 CSV 文件的第一行。执行完毕后,切记关闭文件。
writeCsv << "type,link" << "\n";
search_for_links(parsed_response->root);
writeCsv.close();现在,我们来写内容。在 search_for_links 函数中,当发现 <a> 标签时,不再将其显示在控制台,而是这样做:
if (node->v.element.tag == GUMBO_TAG_A)
{
GumboAttribute* href = gumbo_get_attribute(&node->v.element.attributes, "href");
if (href)
{
std::string link = href->value;
if (link.rfind("/wiki") == 0)
writeCsv << "article," << link << "\n";
else if (link.rfind("#cite") == 0)
writeCsv << "cite," << link << "\n";
else
writeCsv << "other," << link << "\n";
}
}我们通过这段代码获取 href 属性的值,并将其分为三类:文章、引用和其他。
当然,你可以进一步扩展,定义自己的链接类型,例如那些看起来像文章但实际上是文件的链接。
现在你已经知道如何用 C++ 构建自己的网页抓取工具了。很棒,对吧?
不过,你可能曾想过“这简直是得不偿失!”,我们完全理解这种感受。说实话,编写爬虫程序虽是绝佳的学习体验,且适用于小规模数据集,但仅此而已。
若您需要一款快速、可靠且可扩展的数据提取工具,Web 爬取 API 便是最佳选择。因为它集成了您所需的所有功能,例如轮换代理池、JavaScript 渲染、验证码破解、地理定位选项等。
不相信吗?那就亲自体验一下吧!立即开始免费试用 WebScrapingAPI,亲身体验网页抓取是多么简单易行!

Raluca Penciuc 是 WebScrapingAPI 的全栈开发工程师,主要负责开发爬虫、优化规避机制,并探索可靠的方法以降低在目标网站上的被检测概率。