Sumário
- Introdução: por que fazer scraping em C++ e o que convém lembrar
- Como obtemos a página: clientes HTTP
- Trabalho com HTTPS / SSL
- Obtenção do status da resposta e dos cabeçalhos
- Bibliotecas para parsear o conteúdo
- Solução de problemas com codificações e caracteres especiais
- Trabalho com cookies e sessões
- Uso de proxies
- Scraping via TOR
- Multithreading e
curl_multi - Armazenamento de URLs e filas (panorama)
- O que mais é preciso levar em conta
- Principais vantagens e desvantagens da implementação em C++
- Configuração final recomendada
1. Introdução
Web scraping é a obtenção automática de páginas web e a extração de dados estruturados a partir delas. Tecnicamente, a tarefa se divide em duas etapas independentes:
- Etapa de rede — baixar o HTML/JSON por HTTP(S).
- Etapa de parsing — converter o texto «cru» em uma árvore (DOM) e extrair os nós necessários.
C++ não é escolhido para isso por comodidade (em Python ou Go um scraper se escreve várias vezes mais rápido), mas por desempenho e controle: dezenas de milhares de conexões em um único núcleo, consumo mínimo de memória, latência previsível, ausência de pausas do GC e uma integração simples em um backend C/C++ existente.
Antes de escrever código, lembre do lado legal e ético: respeite o robots.txt, não gere carga excessiva (rate limiting), leia os termos de uso do site e a legislação sobre dados pessoais. As técnicas para contornar bloqueios (proxies, TOR) são tratadas mais abaixo como uma ferramenta, não como um convite a descumprir as regras das plataformas.
2. Como obtemos a página
Este é o alicerce de todo o scraper. Em C++ há várias opções, do clássico de baixo nível a wrappers práticos «no estilo Python».
2.1. libcurl — o padrão do setor
A libcurl é o padrão de fato das requisições de rede em C/C++. Suporta HTTP/1.1, HTTP/2, HTTP/3, HTTPS, proxies, SOCKS5, cookies, compressão e timeouts: literalmente tudo o que um scraper precisa, em uma única biblioteca. Seu porém: uma API em C baseada em callbacks, bastante verbosa.
#include <curl/curl.h>
#include <string>
// Callback: o curl o invoca à medida que os dados chegam; nós os acumulamos em uma string.
static size_t write_cb(char* ptr, size_t size, size_t nmemb, void* userdata) {
auto* out = static_cast<std::string*>(userdata);
out->append(ptr, size * nmemb);
return size * nmemb;
}
std::string fetch(const std::string& url) {
CURL* curl = curl_easy_init();
std::string body;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // seguir os redirecionamentos
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); // gzip/deflate/br automático
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; MyBot/1.0)");
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
// tratar curl_easy_strerror(res)
}
curl_easy_cleanup(curl);
return body;
}Repare em CURLOPT_ACCEPT_ENCODING, "": ele ativa a descompressão transparente de gzip/deflate/brotli; sem isso você receberá lixo binário em vez de HTML.
2.2. cpr — «Curl for People»
O cpr (C++ Requests) é um wrapper moderno sobre a libcurl no espírito do Python Requests. Requer C++17 e é mantido ativamente (documentação). A mesma funcionalidade, mas com um código várias vezes mais curto:
#include <cpr/cpr.h>
cpr::Response r = cpr::Get(
cpr::Url{"https://example.com"},
cpr::Header{{"User-Agent", "MyBot/1.0"}},
cpr::Timeout{30000}
);
r.status_code; // 200
r.header["content-type"]; // "text/html; charset=utf-8"
r.text; // corpo da respostaPara a maioria dos projetos é o melhor ponto de partida: você obtém toda a potência da libcurl (proxies, cookies, SSL, requisições assíncronas) com uma API prática. Integra-se via FetchContent do CMake, vcpkg ou Conan.
2.3. cpp-httplib — header-only
O cpp-httplib: um único arquivo de cabeçalho, sem dependências externas (para HTTPS é preciso o OpenSSL). Ideal quando você não quer arrastar o curl. Desvantagens: apenas HTTP/1.1, sem suporte integrado a proxies SOCKS nem à compressão brotli.
#include <httplib.h>
httplib::Client cli("https://example.com");
auto res = cli.Get("/");
if (res && res->status == 200) {
std::string body = res->body;
}2.4. Boost.Beast — baixo nível e assincronia
A Boost.Beast é construída sobre a Boost.Asio e dá controle total sobre HTTP/WebSocket no nível dos sockets com um modelo assíncrono (corrotinas, futures, callbacks). É o caminho para quem precisa de dezenas de milhares de conexões simultâneas e de uma lógica de rede sob medida. O preço: bem mais código; além disso, o HTTPS terá de ser configurado à mão via SSL-stream do Asio.
O que escolher
| Cenário | Recomendação |
|---|---|
| Começar rápido, scraper típico | cpr |
| Máximo controle e recursos, o «padrão» consolidado | libcurl diretamente |
| Mínimo de dependências, cliente simples | cpp-httplib |
| Dezenas de milhares de conexões assíncronas | Boost.Beast / Asio |
3. Trabalho com HTTPS / SSL
Hoje praticamente toda a web é HTTPS, então TLS não é uma opção, é a norma.
3.1. Com libcurl/cpr
A libcurl usa por conta própria uma camada TLS (por padrão o OpenSSL, embora existam compilações com GnuTLS, mbedTLS, BoringSSL, Schannel no Windows ou Secure Transport no macOS). O essencial é a verificação de certificados:
// ATIVADO POR PADRÃO — mude apenas de forma consciente.
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); // verificar a cadeia de certificados
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); // verificar o nome do host
// Indicar um CA-bundle próprio se o do sistema não for encontrado:
curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/cacert.pem");Nunca desative
VERIFYPEER/VERIFYHOSTem produção para «consertar» erros de certificado: isso abre a porta para ataques MITM. Se o conjunto de certificados raiz do sistema não estiver disponível (comum no Windows ou em contêineres), baixe umcacert.pematualizado (publicado pelo projeto curl) e indique-o comCURLOPT_CAINFO.
No cpr, o comportamento padrão é seguro; se necessário, ajusta-se via cpr::SslOptions.
3.2. Detalhes finos
- O SNI (Server Name Indication) vem ativado por padrão; é necessário para os hosts virtuais.
- Versão do TLS: faz sentido forçar no mínimo o TLS 1.2 (
CURLOPT_SSLVERSION = CURL_SSLVERSION_TLSv1_2). - Fingerprinting TLS: os sistemas anti-bots avançados sabem distinguir os clientes pela impressão digital JA3/JA4 do handshake TLS. A impressão digital habitual da libcurl difere da de um navegador; é outro grande tema à parte (que chega a compilações de curl com patches para imitar o ClientHello de um navegador).
4. Status da resposta e cabeçalhos
Um scraper é obrigado a reagir aos códigos HTTP: 200 — correto, 301/302 — redirecionamento, 403/429 — bloqueio ou limite de requisições, 5xx — erro do servidor (tentar de novo mais tarde).
4.1. Código de resposta e cabeçalhos na libcurl
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
char* content_type = nullptr;
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
// O conjunto completo de cabeçalhos é capturado com um callback à parte:
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headers_map);Campos úteis do getinfo: CURLINFO_RESPONSE_CODE, CURLINFO_CONTENT_TYPE, CURLINFO_EFFECTIVE_URL (URL final após os redirecionamentos), CURLINFO_REDIRECT_COUNT, CURLINFO_TOTAL_TIME, CURLINFO_SIZE_DOWNLOAD.
4.2. No cpr tudo chega já parseado
cpr::Response r = cpr::Get(cpr::Url{"https://example.com"});
r.status_code; // 200
r.reason; // "OK"
r.header["content-type"]; // map de cabeçalhos, muito prático
r.url; // URL final
r.elapsed; // duração da requisiçãoPara que isso serve ao scraper: do cabeçalho Content-Type: text/html; charset=iso-8859-1 extrai-se a codificação (veja a seção sobre codificações), e do Retry-After, diante de um 429, quanto é preciso esperar antes de tentar de novo.
5. Bibliotecas para parsear o conteúdo
Você já baixou o HTML: agora é hora de construir o DOM e extrair os nós com seletores. Não parseie HTML com expressões regulares: o layout real, com tags não fechadas, aninhamento e comentários, quebra qualquer regex; use um parser completo.
5.1. lexbor — a opção moderna n.º 1
O lexbor (GitHub) é um parser HTML5 rápido, conforme o padrão WHATWG, escrito em C puro e sem dependências externas. Suporta seletores CSS, detecção da codificação a partir do fluxo de bytes e trabalho com o DOM. É o sucessor de fato do myhtml/Modest e hoje a melhor opção para código novo (é ele, aliás, que o parser do DOM integrado ao PHP 8.4 utiliza).
#include <lexbor/html/parser.h>
#include <lexbor/dom/interfaces/element.h>
lxb_html_document_t* doc = lxb_html_document_create();
lxb_html_document_parse(doc, (const lxb_char_t*)html.data(), html.size());
// depois, percurso do DOM ou busca com o módulo selectors (seletores CSS)
lxb_html_document_destroy(doc);Para dispor de um wrapper C++ prático sobre o lexbor existem projetos de terceiros (por exemplo, o sprexer).
5.2. libxml2 + XPath
A libxml2 é uma biblioteca madura e testada pelo tempo. Seu htmlReadMemory() digere o HTML «sujo», e o XPath oferece seleções poderosas (não traz seletores CSS de fábrica, mas o XPath é mais expressivo). Uma escolha excelente se os seus dados se encaixam bem em expressões XPath.
#include <libxml/HTMLparser.h>
#include <libxml/xpath.h>
htmlDocPtr doc = htmlReadMemory(html.data(), html.size(), nullptr, "UTF-8",
HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
xmlXPathObjectPtr res = xmlXPathEvalExpression((const xmlChar*)"//a/@href", ctx);
// percorrer res->nodesetval->nodeTab5.3. Gumbo — o clássico, mas arquivado
O Gumbo, do Google, foi por muito tempo o padrão do parsing HTML5 em C/C++, mas o repositório está arquivado (somente leitura desde janeiro de 2026). O desenvolvimento continua no fork da comunidade no Codeberg. Para código novo é preferível o lexbor; mencionamos o Gumbo porque você o encontrará em uma infinidade de projetos existentes.
5.4. Outras ferramentas
- htmlcxx — um parser HTML/CSS simples em C++; útil para tarefas leves, mas há muito tempo sem evoluir.
- pugixml e RapidXML — para XML estrito (RSS, sitemaps, SOAP), não para HTML arbitrário.
- JSON: muitos sites entregam os dados via API ou JSON embutido. Escolha o nlohmann/json (praticidade) ou o RapidJSON (velocidade).
O que escolher
| Tarefa | Recomendação |
|---|---|
| Scraper HTML5 novo, seletores CSS necessários | lexbor |
| Seleções complexas, você está habituado ao XPath | libxml2 |
| Manutenção de código legado | Gumbo / fork no Codeberg |
| XML estrito (RSS/sitemap) | pugixml |
| API JSON | nlohmann/json ou RapidJSON |
6. Codificações e caracteres especiais
Uma dor clássica: a página chega com «caracteres estranhos» (mojibake). A causa é quase sempre a mesma: um descompasso de codificações. Na web lusófona convivem o UTF-8 e, nos sites antigos, o windows-1252 (cp1252) ou o ISO-8859-1 (Latin-1). Dentro do programa, mantenha tudo em UTF-8, convertendo na entrada.
6.1. Como detectar a codificação de origem
Fontes (por ordem de prioridade):
- O cabeçalho HTTP
Content-Type: text/html; charset=iso-8859-1. - A meta do HTML:
<meta charset="...">ou<meta http-equiv="Content-Type" content="...; charset=...">. - O BOM no começo do arquivo (para UTF-8/16).
- Heurística sobre o conteúdo (se não houver mais nada).
Uma vantagem: o lexbor sabe detectar a codificação a partir do fluxo de bytes por conta própria, o que elimina boa parte do problema já na fase de parsing.
6.2. Conversão para UTF-8
Opção A — iconv (GNU libiconv), disponível em quase todo lugar:
#include <iconv.h>
// ISO-8859-1 -> UTF-8
iconv_t cd = iconv_open("UTF-8", "ISO-8859-1");
// ... iconv(cd, &in, &inleft, &out, &outleft) ...
iconv_close(cd);Opção B — ICU (International Components for Unicode) — o caminho mais poderoso e confiável: uma lista enorme de codificações, normalização Unicode e um detector de codificação (ucsdet_*):
#include <unicode/ucnv.h>
icu::UnicodeString us(raw.data(), raw.size(), "windows-1252");
std::string utf8;
us.toUTF8String(utf8);Opção C — UTF8-CPP (utfcpp) — uma biblioteca leve header-only para validar, iterar e converter texto já em UTF-8/UTF-16/UTF-32 (não transcodifica cp1252, mas é insubstituível para trabalhar corretamente com o próprio UTF-8).
6.3. Armadilhas práticas
- Não imprima UTF-8 no console do Windows sem
SetConsoleOutputCP(CP_UTF8): você verá lixo mesmo com os dados corretos. - No Windows, para os nomes de arquivo com acentos ou cedilhas use a API wide (
std::wstring/UTF-16). std::stringarmazena bytes, não «caracteres»: para contar caracteres reais (comá,ç,ã) conte por pontos de código (utfcpp/ICU), não com.size().- Fixe sempre o invariante: «na entrada, detecção e conversão para UTF-8; a partir daí, só UTF-8 em todo o pipeline».
7. Cookies e sessões
Muitos sites exigem sessão: login, carrinho, verificações anti-bots, paginação após a autenticação. Os cookies precisam ser aceitos, armazenados e reenviados.
A libcurl traz um «cookie engine» integrado:
// Ativar o motor e conservar os cookies em um arquivo entre execuções:
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt"); // ler (string vazia: ativar apenas o motor em memória)
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt"); // gravar durante o cleanup
// Enviar um cookie específico à mão:
curl_easy_setopt(curl, CURLOPT_COOKIE, "session=abc123; lang=pt");Dentro de uma mesma «sessão», reutilize o mesmo handle CURL (ou um único cpr::Session): assim os cookies, as conexões keep-alive e as sessões TLS se conservam entre as requisições, o que é mais rápido e mais correto do ponto de vista da lógica do site.
No cpr:
cpr::Session session;
session.SetUrl(cpr::Url{"https://example.com/login"});
session.SetCookies(cpr::Cookies{{"session", "abc123"}});
cpr::Response r = session.Get();
cpr::Cookies received = r.cookies; // cookies devolvidos pelo servidorUma armadilha: para que um redirecionamento transporte corretamente os cookies entre subdomínios, ative o cookie engine antes da requisição e não recrie o handle a cada passo.
8. Proxies
Os proxies servem para contornar restrições geográficas, distribuir a carga e reduzir o risco de bloqueio por IP. A libcurl suporta proxies HTTP, HTTPS e SOCKS5 de fábrica.
// Proxy HTTP com autenticação:
curl_easy_setopt(curl, CURLOPT_PROXY, "http://user:pass@proxyhost:8080");
// SOCKS5 (com resolução DNS no lado do proxy — importante para o anonimato):
curl_easy_setopt(curl, CURLOPT_PROXY, "socks5h://proxyhost:1080");No cpr:
cpr::Response r = cpr::Get(
cpr::Url{"https://example.com"},
cpr::Proxies{{"https", "http://user:pass@proxyhost:8080"}}
);Rotação de proxies
Para o scraping em grande escala mantém-se um pool de proxies que vai sendo rotacionado: em círculo, ao acaso ou conforme sua «saúde» (ban → exclusão temporária). A estratégia mais simples: um dicionário {proxy → contador de erros/tempo de repouso} e a escolha de um proxy vivo antes de cada requisição. Convém atribuir a cada proxy seu próprio cookie jar e seu User-Agent, para que as «identidades» não se cruzem.
O prefixo
socks5h://(com a letrah) significa que a resolução DNS passa pelo proxy e não é feita localmente; caso contrário, sua consulta DNS real vai entregá-lo. É crítico com proxies e, sobretudo, com o TOR (mais abaixo).
9. Scraping via TOR
O TOR é um caso particular de proxy SOCKS5: o daemon local do Tor levanta um SOCKS5 normalmente em 127.0.0.1:9050 (o Tor Browser, na 9150). Basta direcionar as requisições para lá:
// Todo o tráfego pela rede Tor; o 'h' resolve o DNS dentro do Tor (obrigatório!):
curl_easy_setopt(curl, CURLOPT_PROXY, "socks5h://127.0.0.1:9050");Troca de «identidade» (novo nó de saída)
Através do ControlPort do Tor (porta 9051) é possível obter com o sinal NEWNYM uma nova cadeia de nós, ou seja, na prática um novo IP. Para isso abrimos um socket TCP para a porta de controle e enviamos comandos conforme o Tor Control Protocol:
AUTHENTICATE "senha"
SIGNAL NEWNYM
QUITIsso permite trocar o IP de saída entre «ondas» de requisições. Na prática, leve em conta:
- Entre um
NEWNYMe outro, o Tor impõe um pequeno período de resfriamento: não dispare o sinal com frequência demais. - O Tor é lento: latência alta e canal estreito. Para o scraping em massa é mais uma ferramenta de anonimato do que de desempenho.
- Muitos sites bloqueiam em bloco os nós de saída conhecidos do Tor (
403/CAPTCHA). - Não passe pelo Tor tarefas em que você de todo modo se autentica com uma conta real: o anonimato se perde no nível da aplicação.
Tecnicamente, a camada de código é a mesma dos proxies (seção 8); só muda o endereço do proxy e se adiciona a lógica de diálogo com a porta de controle.
10. Multithreading
O scraping é uma tarefa I/O-bound: a maior parte do tempo se vai esperando pela rede. O paralelismo multiplica a vazão. Há duas abordagens radicalmente distintas.
10.1. Pool de threads + um handle por thread
O clássico: um pool de threads de trabalho, uma fila de URLs compartilhada e protegida contra concorrência, e em cada thread seu próprio handle CURL.
#include <thread>
#include <queue>
#include <mutex>
std::queue<std::string> urls;
std::mutex m;
void worker() {
CURL* curl = curl_easy_init(); // handle PRÓPRIO por thread
for (;;) {
std::string url;
{
std::lock_guard<std::mutex> lk(m);
if (urls.empty()) break;
url = urls.front(); urls.pop();
}
std::string html = fetch_with(curl, url);
// ... parsing ...
}
curl_easy_cleanup(curl);
}
int main() {
curl_global_init(CURL_GLOBAL_ALL); // UMA única vez, antes de iniciar as threads!
std::vector<std::thread> pool;
for (int i = 0; i < 16; ++i) pool.emplace_back(worker);
for (auto& t : pool) t.join();
curl_global_cleanup();
}Crítico: curl_global_init() é chamado uma única vez na thread principal, antes de lançar os workers; um handle CURL não pode ser compartilhado entre threads: cada thread tem o seu (veja curl: threadsafe).
10.2. curl_multi — muitas conexões em uma única thread
O curl_multi gerencia centenas ou milhares de transferências paralelas em uma única thread por multiplexação (epoll/poll). É mais eficiente em memória do que «uma thread por conexão» e escala maravilhosamente. Seu porém: o código se complica (loop curl_multi_perform + gestão de eventos). É possível combinar as duas abordagens: várias threads, cada uma com sua própria pilha multi.
10.3. Primitivas de paralelismo prontas para usar
std::thread/std::async— os meios básicos da STL.- oneTBB (Intel Threading Building Blocks) — algoritmos paralelos de alto nível, dutos de processamento (o
parallel_pipelineencaixa em cheio no esquema «baixar → parsear → salvar»), contêineres seguros para threads. - OpenMP — paralelização simples de loops com pragmas; costuma render mais no parsing intensivo em CPU do que na espera de rede.
Recomendação
Para a maioria dos projetos: pool de threads (16--64) + um handle por thread. Quando esbarrar na memória ou no número de threads ao chegar a milhares de conexões, passe ao curl_multi.
11. Armazenamento de URLs e filas
(Seção panorâmica: isto já é arquitetura de crawler.) Assim que o scraper começa a seguir links, surge a tarefa de gerenciar o frontier — a fila de URLs ainda não visitadas — e a deduplicação.
Subtarefas-chave:
- Fila de trabalhos (frontier). No caso simples, uma
std::queueem memória. Para resiliência e distribuição, um broker externo: RabbitMQ (cliente rabbitmq-c) ou Redis como fila/conjunto (cliente hiredis). - Deduplicação de URLs. Para não baixar a mesma coisa duas vezes: um
std::unordered_setde URLs normalizadas em memória; com grandes volumes, um filtro de Bloom (compacto, em troca de raros falsos positivos) ou chaves no Redis. - Normalização de URLs. Redução à forma canônica (esquema, host em minúsculas, ordem da query, remoção do
#fragment, resolução de links relativos). Ajuda a Boost.URL. - Persistência. O estado do rastreamento e os resultados, em um banco de dados: SQLite para uma única máquina; PostgreSQL/ClickHouse, para a escala.
- Política de rastreamento. BFS/DFS, prioridades por domínio, rate limiting por host com pausas corteses, limite de profundidade.
Pipeline típico: frontier → baixador (pool/multi) → parser → extrator de links → normalização → dedup → de volta ao frontier, e os dados extraídos, para o armazenamento.
12. O que mais é preciso levar em conta
Temas que muitas vezes são esquecidos, mas sem os quais um scraper «de combate» quebra logo:
robots.txte cortesia. Parseie e respeite orobots.txt(existe o parser oficial google/robotstxt); adicione pausas e limite as RPS por domínio. É o ético e, de quebra, reduz o risco de ban.Proteção anti-bots e impressões digitais. Os sites analisam o User-Agent, o conjunto e a ordem dos cabeçalhos, a impressão digital TLS (JA3/JA4), o framing HTTP/2 e o comportamento. No mínimo, envie cabeçalhos verossímeis (
Accept,Accept-Language,User-Agent) e não «bata na porta» sempre do mesmo jeito. Os CAPTCHAs e os desafios JS são outra camada de problemas.Renderização de JavaScript. libcurl/lexbor só veem o HTML original, sem executar o JS. Para os sites SPA (React/Vue), em que o conteúdo é carregado pelos scripts, é preciso ou um navegador headless (controlar o Chromium via Chrome DevTools Protocol a partir do C++), ou — o que costuma ser mais simples — localizar e chamar diretamente a mesma API JSON interna que o frontend utiliza.
Compressão do conteúdo. Ative o
Accept-Encoding(gzip/deflate/brotli): economiza o tráfego várias vezes; a libcurl descomprime de forma transparente comCURLOPT_ACCEPT_ENCODING. Para descomprimir à mão: zlib e brotli.Novas tentativas e backoff. A rede é instável. Implemente novas tentativas com espera exponencial e jitter, respeite o cabeçalho
Retry-Afternos429/503e fixe timeouts razoáveis (CURLOPT_TIMEOUT,CURLOPT_CONNECTTIMEOUT).Memória e recursos. Feche os handles, libere as árvores DOM (
*_destroy), vigie oContent-Lengthe o limite de tamanho da resposta (CURLOPT_MAXFILESIZE) para não baixar por acidente um arquivo de gigabytes para a memória.Logging e monitoramento. Conte os códigos de resposta, a velocidade e a proporção de bans por domínio; caso contrário, você não saberá quando o seu scraper «morreu em silêncio».
13. Vantagens e desvantagens
Vantagens da implementação em C++
- Desempenho e baixa latência. Velocidade próxima ao hardware, sobrecusto mínimo por requisição.
- Eficiência de memória. É possível sustentar milhares de conexões com recursos modestos (sobretudo com o
curl_multi); não há pausas do coletor de lixo. - Controle total. Ajuste fino de TLS, sockets, timeouts e memória onde as linguagens de alto nível escondem os detalhes.
- Um ecossistema maduro de bibliotecas C. libcurl, OpenSSL, libxml2, ICU, lexbor: um alicerce industrial e comprovado.
- Integração simples em um backend C/C++ existente, sem pontes entre linguagens.
Desvantagens
- Desenvolvimento mais longo e mais caro. O que em Python se escreve em uma tarde, em C++ exige mais código e mais cuidado.
- Gestão manual de recursos. Vazamentos de memória, handles pendurados, condições de corrida no multithreading: responsabilidade sua.
- APIs em C baseadas em callbacks (libcurl, libxml2), verbosas; os wrappers (cpr) atenuam isso em parte.
- Poucas «pilhas incluídas» para os sites com JS. Não há um equivalente nativo do Selenium/Playwright; um navegador headless a partir do C++ é um suplício.
- Menos frameworks de scraping prontos do que em Python (não há um análogo do Scrapy «de fábrica»): escreve-se mais à mão.
Conclusão: C++ compensa quando a escala, a velocidade e o consumo de recursos são críticos (crawlers de alta carga, um produto sobre stack C++). Para tarefas pontuais e protótipos, é mais rápido recorrer a Python ou Go.
14. Configuração final
Um conjunto equilibrado para um scraper de produção em C++:
| Camada | Biblioteca |
|---|---|
| Cliente HTTP | cpr (sobre a libcurl) |
| TLS | OpenSSL (via libcurl) |
| Parsing de HTML | lexbor (ou libxml2 para XPath) |
| JSON | nlohmann/json / RapidJSON |
| Codificações | ICU ou iconv |
| Multithreading | pool de std::thread + curl_multi, e oneTBB se necessário |
| Proxies/TOR | libcurl (socks5h://) + ControlPort do Tor |
| Fila/dedup | Redis / RabbitMQ, filtro de Bloom |
| Armazenamento | SQLite / PostgreSQL |
| Normalização de URLs | Boost.URL |
Comece pelo pequeno — cpr + lexbor + uma única thread — e adicione complexidade (proxies, multithreading, filas) apenas quando a carga real exigir.