在数据驱动的现代世界中,网络刮擦是一种不可或缺的工具,它可以从网站上收集现实生活中的数据,而无需担心应用程序接口的可用性。
在本文中,您将学习如何用 C++ 编写网络刮擦程序。您将使用cpr和libxml2抓取静态网站,使用Selenium抓取动态网站。您还将学习如何在刮擦程序中使用代理。
用 C++ 实现网络抓取
要学习本教程,您需要具备以下条件:
- 现代C++ 编译器(兼容 C++20):可使用GNU Compiler Collection (GCC)、Clang 或MSVC。
- 系统中已安装CMake和GNU Make。
- 您的系统已安装curl和 libxml2。
扫描静态网站
要抓取静态网站,需要使用 cpr 库发出 HTTP 请求,并使用 libxml2 解析响应和提取数据。
在本例中,您将从Quotes to Scrape网站上抓取所有文本和作者姓名,该网站是一个沙盒网站,旨在帮助您练习网络抓取。
注意:如果您试图抓取现实生活中的网站,请务必查看其条款和条件,以避免任何潜在的法律后果。
在抓取任何内容之前,最好先了解 HTML 网页的结构,学习如何提取所需的数据。在浏览器中打开Quotes to Scrape网站,按CTRL + Shift + I 键调出开发人员控制台。
你会发现引文都被包在类quote 的div中。每段引语的正文都在一个带text 类的span中,而每段引语的作者都在一个带author 类的small中:

在机器中为项目创建一个目录:
mkdir cpp-scraper
cd cpp-scraper然后,创建CMakeLists.txt文件,定义如何构建 C++ 项目。您将使用 CMake 作为构建工具,因此如果您不熟悉 CMake 语法,请务必查看相关文档。
将以下代码粘贴到CMakeLists.txt 中:
cmake_minimum_required(VERSION 3.5)
project(scraper VERSION 0.1.0)
include(FetchContent)
FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git
GIT_TAG 3b15fa82ea74739b574d705fea44959b58142eb8)
FetchContent_MakeAvailable(cpr)
add_executable(static-scraper static-scraper.cpp)
target_compile_features(static-scraper PRIVATE cxx_std_20)
target_link_libraries(static-scraper PRIVATE cpr::cpr)
find_package(LibXml2 REQUIRED)
target_link_libraries(static-scraper PRIVATE LibXml2::LibXml2)该文件声明了项目scraper的一个可执行文件,名为static-scraper,由static-scraper.cpp 编译而成。FetchContent_Declare行会从 GitHub 获取 cpr 库,而FetchContent_MakeAvailable调用则会使该库可供链接,链接使用target_link_libraries 完成。最后,使用find_package查找LibXML2软件包,并将可执行文件与之链接。
创建名为static-scraper.cpp的文件,并包含以下必要的头文件:
#include <iostream>
#include "cpr/cpr.h"
#include "libxml/HTMLparser.h"
#include "libxml/xpath.h"
#include <format>然后,创建主函数,在其中编写刮板的逻辑:
<code>int main(int argc, char** argv) {
return 0;
}在主函数中,使用 cpr 向https://quotes.toscrape.com/发送 HTTP GET 请求,如果响应不成功,则中止:
<code>cpr::Response r = cpr::Get(cpr::Url{"https://quotes.toscrape.com/"});
if (r.status_code != 200) {
std::cerr << "Failed to fetch the page" << std::endl;
return 1;
}接下来,使用LibXML2的htmlReadMemory函数将 HTML 响应转换为htmlDocPtr:
<code>htmlDocPtr doc = htmlReadMemory(r.text.c_str(), r.text.length(), nullptr, nullptr, HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
if (doc == nullptr) {
std::cerr << "Failed to parse the page" << std::endl;
return 1;
}注意:在处理指针时,最好先检查指针是否为空,然后再尝试使用。你会看到几乎每一步都会进行这种检查,但文章中不再明确指出。
现在,是时候使用XPath 表达式从文档中提取数据了。从文档中创建 XPath 上下文:
<code>xmlXPathContextPtr context = xmlXPathNewContext(doc);
if (context == nullptr) {
std::cerr << "Failed to create XPath context" << std::endl;
return 1;
}然后,使用 XPath 表达式//div[@class='quote'] 提取所有带有quote 类的div:
<code>xmlXPathObjectPtr quotes = xmlXPathEvalExpression((xmlChar *) "//div[@class='quote']", context);
if (quotes == nullptr) {
std::cerr << "Failed to evaluate XPath expression" << std::endl;
return 1;
}遍历每段引文,提取文本和作者,并打印出来:
<code>for (int i = 0; i < quotes->nodesetval->nodeNr; i++) {
xmlNodePtr quote = quotes->nodesetval->nodeTab[i];
xmlXPathSetContextNode(quote, context);
xmlNodePtr author = xmlXPathEvalExpression((xmlChar *) ".//small[@class='author']", context)->nodesetval->nodeTab[0];
if (author == nullptr) {
std::cerr << "Failed to evaluate XPath expression" << std::endl;
return 1;
}
xmlNodePtr text = xmlXPathEvalExpression((xmlChar *) ".//span[@class='text']", context)->nodesetval->nodeTab[0];
if (text == nullptr) {
std::cerr << "Failed to evaluate XPath expression" << std::endl;
return 1;
}
std::string author_text = reinterpret_cast<const char *>(xmlNodeGetContent(author));
std::string quote_text = reinterpret_cast<const char *>(xmlNodeGetContent(text));
std::cout << std::format("{}: {}", author_text, quote_text) << std::endl;
}整个代码看起来是这样的
#include <iostream>
#include "cpr/cpr.h"
#include "libxml/HTMLparser.h"
#include "libxml/xpath.h"
#include <format>
<code>int main(int argc, char** argv) {
cpr::Response r = cpr::Get(cpr::Url{"https://quotes.toscrape.com/"});
if (r.status_code != 200) {
std::cerr << "Failed to fetch the page" << std::endl;
return 1;
}
htmlDocPtr doc = htmlReadMemory(r.text.c_str(), r.text.length(), nullptr, nullptr, HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
if (doc == nullptr) {
std::cerr << "Failed to parse the page" << std::endl;
return 1;
}
xmlXPathContextPtr context = xmlXPathNewContext(doc);
if (context == nullptr) {
std::cerr << "Failed to create XPath context" << std::endl;
return 1;
}
xmlXPathObjectPtr quotes = xmlXPathEvalExpression((xmlChar *) "//div[@class='quote']", context);
if (quotes == nullptr) {
std::cerr << "Failed to evaluate XPath expression" << std::endl;
return 1;
}
for (int i = 0; i < quotes->nodesetval->nodeNr; i++) {
xmlNodePtr quote = quotes->nodesetval->nodeTab[i];
xmlXPathSetContextNode(quote, context);
xmlNodePtr author = xmlXPathEvalExpression((xmlChar *) ".//small[@class='author']", context)->nodesetval->nodeTab[0];
if (author == nullptr) {
std::cerr << "Failed to evaluate XPath expression" << std::endl;
return 1;
}
xmlNodePtr text = xmlXPathEvalExpression((xmlChar *) ".//span[@class='text']", context)->nodesetval->nodeTab[0];
if (text == nullptr) {
std::cerr << "Failed to evaluate XPath expression" << std::endl;
return 1;
}
std::string author_text = reinterpret_cast<const char *>(xmlNodeGetContent(author));
std::string quote_text = reinterpret_cast<const char *>(xmlNodeGetContent(text));
std::cout << std::format("{}: {}", author_text, quote_text) << std::endl;
}
return 0;
}要编译代码,请创建一个编译目录并移入其中:
mkdir build
cd build使用 CMake 生成构建文件:
cmake ..然后构建可执行文件:
make -j4最后,运行刮刀:
./static-scraper输出结果如下
Albert Einstein: "The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking."
J.K. Rowling: "It is our choices, Harry, that show what we truly are, far more than our abilities."
Albert Einstein: "There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle."
Jane Austen: "The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid."
Marilyn Monroe: "Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring."
Albert Einstein: "Try not to become a man of success. Rather become a man of value."
André Gide: "It is better to be hated for what you are than to be loved for what you are not."
Thomas A. Edison: "I have not failed. I've just found 10,000 ways that won't work."
Eleanor Roosevelt: "A woman is like a tea bag; you never know how strong it is until it's in hot water."
Steve Martin: "A day without sunshine is like, you know, night."扫描动态网站
您刚刚编写的刮擦工具对静态网站很有效,但您的刮擦工具无法执行 JavaScript,这意味着它无法刮擦使用 JavaScript 加载数据的动态网站。要抓取动态网站,你需要一个浏览器自动化工具,如 Selenium,它可以启动浏览器并在其中运行抓取器。
不幸的是,Selenium 没有官方的 C++ 绑定,所以你需要使用 Selenium 服务器和webdriverxx库来向服务器发送命令。当前版本的库与最新版本的 Selenium Server 有冲突,因此你需要使用旧版本的提交和 Selenium Server 的 4.0.0 版本,这也是据说能正常工作的最后一个版本。
要继续学习,您需要在计算机上安装最新版本的Java。您还需要一个WebDriver及其相应的浏览器。本文使用 Geckodriver 和 Firefox,但您也可以使用自己喜欢的浏览器。
在本示例中,您将抓取奥斯卡获奖影片页面,这是另一个沙盒网站。如果打开该页面并点击任何按钮,就会使用 Ajax 和 JavaScript 加载当年的奥斯卡获奖影片表。电影名称存储在类为film-title 的td元素中:

在CMakeLists.txt中添加另一行FetchContent_Declare,以获取webdriverxx库。然后,修改FetchContent_MakeAvailable行以包含webdriverxx:
FetchContent_Declare(webdriverxx GIT_REPOSITORY https://github.com/GermanAizek/webdriverxx
GIT_TAG 0b04c449b6f187ecff67addaf1a22ae23a43afd9
GIT_SUBMODULES "")
FetchContent_MakeAvailable(cpr webdriverxx) # Add webdriverxx here在文件末尾,粘贴以下代码,声明另一个可执行文件,并将其与 libcurl 和webdriverxx 链接:
add_executable(dynamic-scraper dynamic-scraper.cpp)
target_compile_features(dynamic-scraper PRIVATE cxx_std_20)
find_package(CURL REQUIRED)
include_directories(${CURL_INCLUDE_DIR})
link_directories(${CURL_LIBRARIES})
target_include_directories(dynamic-scraper PRIVATE "${webdriverxx_SOURCE_DIR}/src/include")
target_link_libraries(dynamic-scraper PRIVATE ${CURL_LIBRARIES})创建名为dynamic-scraper.cpp的文件,并将库包含在内:
#include "webdriverxx.h"
#include <iostream>
#include <vector>
#include <thread>
#include <chrono>
using namespace webdriverxx;在主函数中,启动 Firefox 实例:
<code>int main() {
WebDriver ff = Start(Firefox());
return 0;
}浏览奥斯卡获奖影片网页,找到2015按钮。点击该按钮,等待五秒钟载入表格:
<code>ff.Navigate("https://www.scrapethissite.com/pages/ajax-javascript");
ff.FindElement(ById("2015")).Click();
std::this_thread::sleep_for(std::chrono::seconds(5));然后,找到带有影片类型标题的元素并打印其内容:
<code>auto find_films = [&]() { return ff.FindElements(ByClass("film-title")); };
std::vector<Element> films = WaitForValue(find_films);
std::cout << "Found " << films.size() << " films" << std::endl;
for (Element film : films) {
std::cout << film.GetText() << std::endl;
}整个代码看起来是这样的
#include "webdriverxx.h"
#include <iostream>
#include <vector>
#include <thread>
#include <chrono>
using namespace webdriverxx;
<code>int main() {
WebDriver ff = Start(Firefox());
ff.Navigate("https://www.scrapethissite.com/pages/ajax-javascript");
ff.FindElement(ById("2015")).Click();
std::this_thread::sleep_for(std::chrono::seconds(5));
auto find_films = [&]() { return ff.FindElements(ByClass("film-title")); };
std::vector<Element> films = WaitForValue(find_films);
std::cout << "Found " << films.size() << " films" << std::endl;
for (Element film : films) {
std::cout << film.GetText() << std::endl;
}
return 0;
}下载Selenium 服务器并用 Java 运行:
java -jar selenium-server-4.0.0.jar standalone保持该终端打开。
在另一个终端中,确保你在构建目录中,然后编译项目:
cmake .. && make -j4然后,运行刮刀:
./dynamic-scraper火狐浏览器的一个实例将启动,一段时间后,您将得到以下输出:
Found 16 films
Spotlight
Mad Max: Fury Road
The Revenant
Bridge of Spies
The Big Short
The Danish Girl
Room
Ex Machina
The Hateful Eight
Inside Out
Amy
Bear Story
A Girl in the River: The Price of Forgiveness
Son of Saul
Spectre
Stutterer使用代理进行网络抓取
在许多不同的情况下,您都需要使用代理进行网络搜刮。代理可以从网站上隐藏您的 IP 地址,这有助于保护您的隐私和避免 IP 禁止。代理还可以让您访问被地理封锁或受限制的内容。
要使用代理,您需要访问代理服务器。为了达到最佳效果,您可以购买高级代理服务器,但出于测试目的,您可以从免费代理服务器列表网站上选择一个免费代理服务器。您需要从列表中获取代理服务器的 IP 地址和端口。
要在 cpr 中使用代理,只需在 cpr::Get调用中使用cpr::Proxies传递代理详细信息:
cpr::Response r = cpr::Get(cpr::Url{"https://quotes.toscrape.com/"}, cpr::Proxies{{"http", "http://IP_ADDRESS:PORT"}});如果代理服务器需要身份验证,请使用cpr::ProxyAuthentication 传递凭据:
cpr::Response r = cpr::Get(cpr::Url{"https://quotes.toscrape.com/"}, cpr::Proxies{{"http", "http://IP_ADDRESS:PORT"}}, cpr::ProxyAuthentication{{"http", EncodedAuthentiction{"USERNAME", "PASSWORD"}}});要在webdriverxx 中使用代理,请使用SetProxy传递代理详细信息:
<code>WebDriver ff = Start(Firefox().SetProxy(
HttpProxy("IP_ADDRESS:PORT")
.SetUsername("USERNAME")
.SetPassword("PASSWORD")
));这就是你使用代理所需要的一切。您可以编译并运行如前所示的代码。您将得到相同的输出结果,但这次,您的请求将通过代理服务器路由。
您可以在GitHub 上找到本文的所有代码。
结论
在这篇文章中,你学习了如何使用 C++ 编写网络搜索器。你将学习如何集成 cpr 和 libxml2 来构建静态网站刮擦程序,以及如何使用 Selenium 来编写动态网站刮擦程序。有了这些知识,您就可以创建更高级的刮擦程序,从而能够处理现实生活中的网站。数据挖掘和竞争对手分析等各种应用场景都需要快速、高效的网站抓取程序,而您可以使用 C++ 编写这些程序。
这篇文章还向你展示了如何在 C++ Web scraper 中添加代理。代理对保护你的网络隐私至关重要。它们可以充当卫士,掩护你的真实 IP 地址。它们还能帮助你规避 IP 禁止和地理封锁。
寻找最佳代理服务器?阅读我们的最佳代理提供商评论。
常见问题
对于 C++ 网络搜刮,您需要使用 cpr 进行 HTTP 请求,使用 libxml2 解析 HTML 并从静态网站中提取数据,使用 Selenium 和 webdriverxx 库进行动态网站搜刮。您还需要 GCC、Clang 或 MSVC 等现代 C++ 编译器(兼容 C++20)以及 CMake 和 GNU Make。
要抓取静态网站,可使用 cpr 库发出 HTTP GET 请求以获取页面内容,然后使用 libxml2 的 htmlReadMemory 函数解析 HTML 响应。使用 XPath 表达式从解析后的文档中提取特定的数据元素。例如,使用 //div[@class=’quote’] 提取所有类为 “quote “的 div 元素。
是的,C++ 可以使用 Selenium Server 和 webdriverxx 库抓取动态网站。这种方法可启动浏览器(如使用 Geckodriver 的 Firefox)并在其中运行刮擦工具,从而允许执行 JavaScript。浏览器自动化工具可执行点击按钮和等待元素加载等操作。
XPath(XML 路径语言)是一种查询语言,用于导航和选择 XML/HTML 文档中的元素。在 C++ 网络搜刮中,XPath 表达式如 //div[@class=’quote’] 或 .//small[@class=’author’] 允许您使用 libxml2 从解析的 HTML 文档中精确定位和提取特定元素。
对于 cpr 库,在 cpr::Get 调用中使用 cpr::Proxies 传递代理详细信息:cpr::Proxies{{“http”, “http://IP_ADDRESS:PORT”}}。对于经过身份验证的代理,请添加 cpr::ProxyAuthentication 并输入凭据。对于使用 Selenium 的 webdriverxx,请使用 SetProxy 方法:Firefox().SetProxy(HttpProxy(“IP:PORT”).SetUsername(“user”).SetPassword(“pass”)).
您需要一个现代的 C++20 兼容编译器(GCC、Clang 或 MSVC)、CMake 和 GNU Make 编译工具、已安装的 curl 和 libxml2 库、用于 Selenium Server 的 Java,以及一个 WebDriver(如 Geckodriver)和相应的浏览器,以便进行动态刮擦。
使用 CMake 作为构建工具。创建 CMakeLists.txt 文件,定义项目配置,使用 FetchContent_Declare 获取所需库函数,创建刮板代码(static-scraper.cpp 或 dynamic-scraper.cpp),创建构建目录,使用 “cmake … “生成构建文件,使用 “make -j4 “构建可执行文件。
C++ 非常适合需要快速高效的网络刮擦程序的场景,如大规模数据挖掘和竞争对手分析。虽然 C++ 的设置比 Python 更复杂,但它在处理大量数据时具有卓越的性能和速度,因此对性能要求极高的刮擦任务非常有价值。
在处理之前,始终检查 HTTP 响应状态代码(r.status_code != 200)。使用指针前检查指针是否为空(if doc == nullptr)。使用 std::cerr 实现正确的错误处理,以记录错误,并返回适当的错误代码,以指示刮擦函数中的失败。
GitHub 上提供了完整的 C++ 网络搜刮代码示例,包括集成了代理的静态和动态搜刮器。这些示例演示了使用 libxml2 和 Selenium 方法从沙盒网站(如 Quotes to Scrape 和 Oscar Winning Films 网页)刮除报价。
Leave a Comment
Required fields are marked *