Парсинг на C++: когда скорость решает всё
В мире сбора данных Python давно стал мейнстримом. Requests, BeautifulSoup, Scrapy — эти инструменты знакомы каждому, кто хоть раз сталкивался с парсингом. Но что делать, если нужно обрабатывать тысячи страниц в секунду, а ресурсы сервера не резиновые? Или если проект требует максимальной производительности и минимального потребления памяти?
Тогда на сцену выходит парсинг на C++. Да, этот язык не так прост в освоении, как Python, но он даёт то, что не могут дать интерпретируемые языки — скорость на уровне железа и полный контроль над памятью. В этой статье мы разберём, как организовать скрапинг данных на C++, какие библиотеки использовать и почему иногда C++ оказывается лучшим выбором для парсинга.
Почему C++ для парсинга — это не безумие?
Многие считают, что C++ слишком сложен для такой задачи, как сбор данных с веб-страниц. Отчасти это правда: написать парсер на Python можно за 15 минут, а на C++ придётся потратить час. Но есть сценарии, где C++ незаменим:
- High-load системы — когда нужно парсить миллионы страниц ежедневно.
- Микросервисы на C++ — если весь бэкенд написан на C++, проще добавить парсер туда же.
- Ограниченные ресурсы — встраиваемые системы, IoT, где каждый мегабайт на счету.
- Требования к стабильности — C++ позволяет избежать проблем с утечками памяти при правильном подходе.
Ключевое преимущество скрапинга на C++ — скорость. Нативный код выполняется в разы быстрее Python, особенно при работе с регулярными выражениями и разборе больших HTML-документов.
Инструментарий для парсинга на C++
Для работы с HTTP-запросами и парсинга HTML/XML нам понадобятся проверенные библиотеки. Вот основной джентльменский набор для парсинга сайтов на C++:
1. libcurl — работа с HTTP
libcurl — это швейцарский нож для сетевых запросов. Он поддерживает HTTP/HTTPS, прокси, куки, таймауты и многое другое. Это стандарт де-факто для C++.
#include <curl/curl.h>
#include <string>
size_t writeCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
size_t totalSize = size * nmemb;
output->append((char*)contents, totalSize);
return totalSize;
}
std::string fetchPage(const std::string& url) {
CURL* curl = curl_easy_init();
std::string response;
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0");
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
std::cerr << "curl error: " << curl_easy_strerror(res) << std::endl;
}
curl_easy_cleanup(curl);
}
return response;
}
2. Gumbo-parser — разбор HTML
Gumbo-parser от Google — это библиотека для парсинга HTML5, написанная на C. Она проста, быстра и не требует зависимостей. Отлично подходит для парсинга HTML на C++.
#include <gumbo.h>
#include <iostream>
void extractLinks(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 << "Found link: " << href->value << std::endl;
}
}
const GumboVector& children = node->v.element.children;
for (unsigned int i = 0; i < children.length; ++i) {
extractLinks(static_cast<GumboNode*>(children.data[i]));
}
}
void parseHTML(const std::string& html) {
GumboOutput* output = gumbo_parse(html.c_str());
extractLinks(output->root);
gumbo_destroy_output(&kGumboDefaultOptions, output);
}
3. PugiXML — работа с XML и XPath
Если сайт отдаёт данные в XML или вы парсите RSS-ленты, PugiXML — лучший выбор. Он легковесный и поддерживает XPath.
#include <pugixml.hpp>
void parseXML(const std::string& xmlContent) {
pugi::xml_document doc;
if (!doc.load_string(xmlContent.c_str())) {
std::cerr << "XML parse error" << std::endl;
return;
}
// XPath запрос
pugi::xpath_node_set titles = doc.select_nodes("//item/title");
for (auto& node : titles) {
std::cout << "Title: " << node.node().text().get() << std::endl;
}
}
Практический пример: парсинг заголовков новостей
Давайте соберём всё вместе и напишем полноценный пример парсинга данных на C++. Допустим, мы хотим получить заголовки новостей с главной страницы Habr (или любого другого сайта).
#include <iostream>
#include <string>
#include <vector>
#include <curl/curl.h>
#include <gumbo.h>
// Callback для libcurl (такой же, как выше)
size_t writeCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
size_t totalSize = size * nmemb;
output->append((char*)contents, totalSize);
return totalSize;
}
// Рекурсивный поиск текста внутри тегов h2
void extractTitles(GumboNode* node, std::vector<std::string>& titles) {
if (node->type != GUMBO_NODE_ELEMENT) {
if (node->type == GUMBO_NODE_TEXT) {
// Нам нужны только тексты внутри h2
}
return;
}
if (node->v.element.tag == GUMBO_TAG_H2) {
// Извлекаем весь текст внутри h2
GumboNode* textNode = static_cast<GumboNode*>(node->v.element.children.data[0]);
if (textNode && textNode->type == GUMBO_NODE_TEXT) {
titles.push_back(textNode->v.text.text);
}
}
const GumboVector& children = node->v.element.children;
for (unsigned int i = 0; i < children.length; ++i) {
extractTitles(static_cast<GumboNode*>(children.data[i]), titles);
}
}
int main() {
std::string html