🎯 一款可定制、具备反检测功能的云浏览器,由自主研发的 Chromium驱动,专为网页爬虫AI 代理设计。👉立即试用
返回博客

C++ 网络爬虫:性能关键项目的实用指南

Michael Lee
Michael Lee

Expert Network Defense Engineer

30-Jun-2026

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 Copy
sudo apt-get install -y libcurl4-openssl-dev libxml2-dev
bash Copy
export SCRAPELESS_API_KEY="your_api_token_here"

第一步 - 使用 libcurl POST 目标 URL

API 接收一个 JSON 请求体,命名操作员(unlocker.webunlocker)和输入——目标 URL、方法和是否渲染 JavaScript。身份验证使用 x-api-token 头。libcurl 将响应收集到一个 std::string

cpp Copy
#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 Copy
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 Copy
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 Copy
#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 Copy
g++ scraper.cpp -o scraper -I/usr/include/libxml2 -lcurl -lxml2
./scraper

针对 JavaScript 渲染目录页面的实时运行返回:

text Copy
http_status=200 html_bytes=51295 books=20
first_book=A Light in the Attic

这就是整个循环:一个 HTTPS POST 请求,51 KB 的渲染 HTML 返回,20 个标题通过 libxml2 解析——没有嵌入浏览器,没有代理池,您的二进制文件中没有反机器人代码。


您得到的回报

API 信封小且可预测:

json Copy
{
  "code": 200,
  "data": "<html>...渲染的 DOM...</html>"
}
// 来自实时调用的真实形状。data 保存 JSON 转义的渲染 HTML(在验证的运行中为 51,295 字节)。

一些诚实的观察:

  • js_render: true 增加延迟但获取内容。 对于静态页面,请关闭它以加快速度;当页面在没有它的情况下是空白时请打开它。
  • 对于复杂的响应使用真正的 JSON 库。 手工制作的提取器处理单个 data 字段;任何更复杂的内容都应有一个合适的解析器。
  • XPath 胜过字符串匹配。 libxml2 的 XPath 比手写的 HTML 扫描更快更稳健。
  • 释放 libxml2 对象。 xmlFreeDocxmlXPathFreeContextxmlXPathFreeObject——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 的内存泄漏?
始终释放你分配的内容:xmlXPathFreeObjectxmlXPathFreeContextxmlFreeDoc。libxml2 使用手动内存管理。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录