C++ 网络爬虫:性能关键项目的实用指南
Expert Network Defense Engineer
TL;DR:
- C++ 是一个出色的抓取 解析器,但却是一个尴尬的抓取 客户端。 libxml2 以原生速度解析 HTML,但在 C++ 中编写 TLS 指纹识别、代理轮换和 JavaScript 渲染是一个独立的项目。
- 分拆工作:让渲染 API 来抓取,让 C++ 来解析。 libcurl 将目标 URL POST 到 Scrapeless 通用抓取 API,该 API 返回渲染的 HTML;libxml2 + XPath 在本地快速提取。
js_render: true是将空白页面变成数据的关键。 许多网站使用 JavaScript 构建其内容;API 在服务器端运行它,因此你的 C++ 代码接收到的是完成的 DOM,而不是空壳。- 整个客户端只需要两个你已经熟悉的库。 libcurl 用于 HTTP POST,libxml2 用于 HTML + XPath — 不需要嵌入无头浏览器,也不需要在 C++ 中维护反机器人堆栈。
- 它经过验证,小巧。 以下完整程序通过一行
g++编译,并且现场运行:HTTP 200,51 KB 的渲染 HTML,解析了 20 个产品标题。 - 免费入门。 新的 Scrapeless 账户包括免费的运行时 — 可在 app.scrapeless.com 注册。
介绍:C++ 在抓取中的角色
C++ 在抓取中出现的唯一原因是:速度。当你在解析数百万页面时,libxml2 以 原生速度 解析 HTML 明显优于一个解释器解析器。问题出在解析 之前。现代的目标将访问控制在 TLS 指纹识别和反机器人防御、IP 声誉和必须实际运行 JavaScript 的内容后面。在 C++ 中再现这些内容——一个隐形 HTTP 堆栈、一个代理池、一个嵌入浏览器引擎——远比抓取本身要复杂得多。
务实的拆分是停止让 C++ 做它不擅长的部分。使用渲染 API 来处理抓取——TLS、代理、JavaScript、反机器人——然后将完成的 HTML 交给 C++ 解析。你的程序变成两个易于理解的部分:libcurl 发出 一个 HTTPS POST,而 libxml2 在结果上运行 XPath。
本指南正是利用 Scrapeless 通用抓取 API 构建的。在最后的完整程序中已编译并现场运行;其中的数字是真实捕获的数据。
你可以用它做什么
- 以原生速度解析 — 对渲染的 HTML 进行 libxml2 + XPath 解析,没有解释器开销。
- 抓取 JavaScript 密集型网站 而无需在你的 C++ 二进制中嵌入浏览器。
- 跳过反机器人堆栈 — TLS 指纹识别、代理轮换和 CAPTCHA 处理都在 API 中,而不是在你的代码里。
- 将抓取集成到现有的 C++ 系统中 — 一个交易管道、一个数据工具、一个本地服务 — 只需 libcurl 和 libxml2。
- 横向扩展 — 渲染发生在服务器端,因此你的二进制文件保持轻量级。
为什么选择 Scrapeless 通用抓取 API
Scrapeless 通用抓取 API 接收目标 URL 并返回渲染的、未阻止的 HTML。专为 C++ 客户端而设计,提供:
- 服务器端 JavaScript 渲染 —
js_render: true返回完成的 DOM,因此 libxml2 看到真实内容。 - 遍布 195+ 个国家的住宅代理 — 抓取从受信 IP 进行;你无需管理代理池。
- 反机器人处理 — TLS 指纹识别和挑战解决发生在 API 端,而不是你的二进制代码中。
- 一个简单的 HTTPS POST — 无需链接 SDK;libcurl 就是你所需的所有 C++。
- 一个简单的封装 —
{"code":200,"data":"<html>…"},易于处理,无需重量级的 JSON 依赖。
在 app.scrapeless.com 的免费计划上获取你的 API 密钥。
先决条件
- 一个 C++17 编译器(g++ 或 clang)
- libcurl 和 libxml2 的开发头文件
- 一个 Scrapeless 账户和 API 密钥 — 可在 app.scrapeless.com 注册
在 Debian/Ubuntu 上:
bash
sudo apt-get install -y libcurl4-openssl-dev libxml2-dev
bash
export SCRAPELESS_API_KEY="your_api_token_here"
第一步 - 使用 libcurl POST 目标 URL
API 接收一个 JSON 请求体,命名操作员(unlocker.webunlocker)和输入——目标 URL、方法和是否渲染 JavaScript。身份验证使用 x-api-token 头。libcurl 将响应收集到一个 std::string:
cpp
#include <curl/curl.h>
#include <string>
static size_t writeCb(char* ptr, size_t size, size_t nmemb, void* userdata) {
static_cast<std::string*>(userdata)->append(ptr, size * nmemb);
return size * nmemb;
}
std::string fetchRendered(const std::string& url, const char* apiKey) {
std::string payload =
"{\"actor\":\"unlocker.webunlocker\",\"input\":{"
"\"url\":\"" + url + "\",\"method\":\"GET\","
"\"redirect\":true,\"js_render\":true}}";
CURL* curl = curl_easy_init();
std::string resp;
cpp
curl_slist* hdrs = nullptr;
hdrs = curl_slist_append(hdrs, "Content-Type: application/json");
hdrs = curl_slist_append(hdrs, (std::string("x-api-token: ") + apiKey).c_str());
curl_easy_setopt(curl, CURLOPT_URL, "https://api.scrapeless.com/api/v1/unlocker/request");
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L);
curl_easy_perform(curl);
curl_slist_free_all(hdrs);
curl_easy_cleanup(curl);
return resp;
}
js_render: true 是支撑标志:它告诉 API 运行页面的 JavaScript 并返回完成的 DOM,因此一个在客户端构建内容的网站返回的是实际的标记。
在免费计划上获取您的 API 密钥:app.scrapeless.com
第 2 步 — 从信封中提取 HTML
API 返回 {"code":200,"data":"<html>…"},其中 HTML 在 data 中进行了 JSON 转义。一个小提取器取消转义该字段——这个形状不需要重型 JSON 库:
cpp
std::string jsonGetData(const std::string& body) {
const std::string key = "\"data\":\"";
size_t i = body.find(key);
if (i == std::string::npos) return "";
i += key.size();
std::string out;
for (; i < body.size(); ++i) {
char c = body[i];
if (c == '\\') { // 取消转义 \" \\ \n \r \t \uXXXX
char n = body[++i];
if (n == 'n') out += '\n';
else if (n == 'r') out += '\r';
else if (n == 't') out += '\t';
else if (n == 'u') { out += (char)std::stoi(body.substr(i + 1, 4), nullptr, 16); i += 4; }
else out += n;
} else if (c == '"') break;
else out += c;
}
return out;
}
(对于生产环境中的许多字段类型,链接一个真正的 JSON 库——但对于这个信封,上面的提取器就足够了。)
第 3 步 — 使用 libxml2 和 XPath 进行解析
现在是快速的部分。libxml2 解析 HTML;XPath 精确选择您想要的节点。这里从目录页面中提取产品标题:
cpp
#include <libxml/HTMLparser.h>
#include <libxml/xpath.h>
#include <iostream>
void extractTitles(const std::string& html) {
htmlDocPtr doc = htmlReadMemory(html.c_str(), html.size(), nullptr, nullptr,
HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
xmlXPathObjectPtr r = xmlXPathEvalExpression(
(const xmlChar*)"//article[@class='product_pod']//h3/a/@title", ctx);
int n = r->nodesetval ? r->nodesetval->nodeNr : 0;
std::cout << "books=" << n << "\n";
for (int i = 0; i < n; ++i) {
xmlChar* t = xmlNodeGetContent(r->nodesetval->nodeTab[i]);
std::cout << t << "\n";
xmlFree(t);
}
xmlXPathFreeObject(r);
xmlXPathFreeContext(ctx);
xmlFreeDoc(doc);
}
第 4 步 — 编译和运行
链接这两个库并将编译器指向 libxml2 头文件:
bash
g++ scraper.cpp -o scraper -I/usr/include/libxml2 -lcurl -lxml2
./scraper
针对 JavaScript 渲染目录页面的实时运行返回:
text
http_status=200 html_bytes=51295 books=20
first_book=A Light in the Attic
这就是整个循环:一个 HTTPS POST 请求,51 KB 的渲染 HTML 返回,20 个标题通过 libxml2 解析——没有嵌入浏览器,没有代理池,您的二进制文件中没有反机器人代码。
您得到的回报
API 信封小且可预测:
json
{
"code": 200,
"data": "<html>...渲染的 DOM...</html>"
}
// 来自实时调用的真实形状。data 保存 JSON 转义的渲染 HTML(在验证的运行中为 51,295 字节)。
一些诚实的观察:
js_render: true增加延迟但获取内容。 对于静态页面,请关闭它以加快速度;当页面在没有它的情况下是空白时请打开它。- 对于复杂的响应使用真正的 JSON 库。 手工制作的提取器处理单个
data字段;任何更复杂的内容都应有一个合适的解析器。 - XPath 胜过字符串匹配。 libxml2 的 XPath 比手写的 HTML 扫描更快更稳健。
- 释放 libxml2 对象。
xmlFreeDoc,xmlXPathFreeContext,xmlXPathFreeObject——C++ 不会为您处理这些。
结论:C++ 用于解析,API 用于获取
性能关键的 C++ 抓取在 C++ 擅长的领域——快速解析——和渲染 API 处理那些原生构建起来痛苦的部分(如 JavaScript 执行、代理和反机器人处理)时效果最佳。使用 libcurl 进行一个 HTTPS POST 和 libxml2 进行 XPath 提取,整个客户端仅由两个熟悉的库和一行 g++ 代码组成。要在其他语言中实现相同的获取然后解析的分离,参见 Scrapling + Scrapeless 指南;通用抓取 API 产品页面 和 文档 涵盖了完整的 API。在页面需要时进行服务器端渲染,使用 XPath 进行解析,并释放你的 libxml2 对象。
准备好构建你的 AI 驱动数据管道了吗?
加入我们的社区以索取免费计划,并与构建原生抓取器的开发人员联系: Discord · Telegram。
在 app.scrapeless.com 注册以获取免费的运行时,并将上述程序调整为符合你 C++ 管道需要的网站和选择器。请查看 定价 以获取规模信息。
FAQ
问:为什么不直接用 libcurl 在纯 C++ 中编写整个抓取器?
对于简单的、未受保护的页面可以这么做。但一旦目标需要 JavaScript 渲染、住宅 IP 或反机器人处理,在 C++ 中复制这些是一个庞大的项目。将获取工作委托给渲染 API 可以保持你的二进制文件小巧。
问:我需要在我的 C++ 程序中嵌入无头浏览器吗?
不需要。js_render: true 在服务器端运行 JavaScript 并返回完成的 DOM,因此你的 C++ 代码仅需解析 HTML。
问:libxml2 是合适的解析器吗?
对于大规模的 HTML,答案是肯定的——它速度快,成熟,并且支持完整的 XPath。如果你愿意,CPR 或 Boost.Beast 可以替代 libcurl 处理 HTTP,但 libxml2 是解析的标准选择。
问:我如何在没有大依赖项的情况下处理 JSON 响应?
对于这里显示的单个 data 字段,小型提取器就可以。对于更丰富的响应,连接一个轻量级的 JSON 库而不是手动解析。
问:我需要管理代理吗?
不需要。API 通过住宅代理进行出口;你只需传入 URL 并返回 HTML。
问:我如何避免 libxml2 的内存泄漏?
始终释放你分配的内容:xmlXPathFreeObject、xmlXPathFreeContext 和 xmlFreeDoc。libxml2 使用手动内存管理。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



