---
title: "Rust para Web Crawling e Scraping em 2026: reqwest, scraper e Boas Práticas"
url: "https://rustlang.com.br/blog/rust-web-crawling-scraping-reqwest-scraper-2026/"
markdown_url: "https://rustlang.com.br/blog/rust-web-crawling-scraping-reqwest-scraper-2026.MD"
description: "Como construir crawlers e scrapers em Rust com reqwest, scraper, fantoccini e Tokio em 2026: arquitetura, concorrência, respeito a robots.txt, proxies, rate limit e erros em produção."
date: "2026-06-21"
author: "Equipe Rust Brasil"
---

# Rust para Web Crawling e Scraping em 2026: reqwest, scraper e Boas Práticas

Como construir crawlers e scrapers em Rust com reqwest, scraper, fantoccini e Tokio em 2026: arquitetura, concorrência, respeito a robots.txt, proxies, rate limit e erros em produção.


## Por que Rust é uma base sólida para crawling e scraping

Coletar dados da web em escala é um problema de engenharia tanto quanto de parsing. O trabalho envolve milhares de conexões simultâneas, controle fino de timeouts, retentativas, filas de URLs, respeito aos limites do servidor, tratamento de respostas parciais e tolerância a falhas por domínio. Rust oferece exatamente o perfil de ferramentas para isso: um runtime assíncrono maduro com [Tokio](/ecossistema/tokio/), um cliente HTTP de alto nível com [Reqwest](/ecossistema/reqwest/) sobre [Hyper](/ecossistema/hyper/), e tipos que impedem grande parte dos bugs de memória e concorrência que assombram pipelines equivalentes em linguagens dinâmicas.

Para quem busca [vagas Rust](/vagas/) e quer construir carreira em backend, engenharia de dados ou infraestrutura, escrever um crawler robusto é um exercício completo. Ele exige entender bem async, canais, isolamento de erros, configuração, observabilidade com tracing e testes. O ecossistema brasileiro de empresas usando Rust cresce em fintechs, logtechs e plataformas de dados, e coleta de dados públicos aparece com frequência em entrevistas e projetos reais. Quem já construiu o [projeto web crawler da comunidade](/projetos/crawler/) encontra aqui uma continuação natural: mesma base, mas com scraping estruturado e boas práticas de produção.

## O kit básico: reqwest + scraper + tokio

A pilha mais comum para coleta de dados em Rust começa com três crates. `reqwest` cuida das requisições HTTP assíncronas. `scraper` faz a seleção de elementos no HTML usando seletores CSS, sobre uma árvore construída com `html5ever`, a mesma base usada por navegadores do projeto Servo. `tokio` provê o runtime e utilidades como canais, timers e tarefas concorrentes.

```toml
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json", "gzip"] }
scraper = "0.20"
anyhow = "1"
```

Um exemplo mínimo de scraping estruturado:

```rust
use anyhow::Result;
use scraper::{Html, Selector};

#[tokio::main]
async fn main() -> Result<()> {
    let client = reqwest::Client::builder()
        .user_agent("rustlang-br-crawler/1.0 (+https://rustlang.com.br)")
        .build()?;

    let resp = client
        .get("https://example.com/artigos")
        .send()
        .await?
        .error_for_status()?
        .text()
        .await?;

    let document = Html::parse_document(&resp);
    let titulo = Selector::parse("h1.titulo")?;
    let links = Selector::parse("a.lista__item")?;

    for el in document.select(&links) {
        let href = el.value().attr("href").unwrap_or_default();
        let texto = el.text().collect::<Vec<_>>().join("");
        println!("{href} -> {}", texto.trim());
    }

    Ok(())
}
```

Note três decisões importantes que já aparecem nesse trecho. Primeiro, o `user_agent` é explícito e identificável, com URL de contato. Segundo, usamos `error_for_status()` para transformar respostas HTTP 4xx e 5xx em erros Rust, evitando processar HTMLs de erro como se fossem conteúdo. Terceiro, mantemos o `Client` reutilizado entre chamadas: ele mantém o pool de conexões e o cache de TLS, o que reduz drasticamente o custo de muitas requisições ao mesmo domínio.

## Arquitetura de um crawler de verdade

O exemplo acima é suficiente para extração pontual, mas um crawler de produção precisa de uma arquitetura explícita. Os componentes costumam ser cinco: uma **fronteira de URLs**, um **dispatcher** com controle de concorrência por domínio, um **fetcher** de páginas, um **parser** que extrai dados e novos links e um **sink** que persiste o resultado.

A fronteira de URLs é uma fila persistentável. Pode ser um banco como PostgreSQL via [sqlx](/ecossistema/sqlx/) ou SQLite embutido, um Redis ou uma fila de mensagens. O importante é que cada URL tenha estado (`pendente`, `em_progresso`, `feito`, `erro`) para permitir retomada sem retrabalho. Um crawler que cai no meio da execução e recomeça tudo do zero se torna um problema operacional rápido.

O dispatcher controla paralelismo. A armadilha clássica é abrir centenas de tarefas contra o mesmo domínio e tomar `429 Too Many Requests` ou um banimento. A solução é manter uma fila separada por domínio e um semáforo por domínio limitando a concorrência. O Tokio oferece `tokio::sync::Semaphore` e canais como `mpsc` que cabem perfeitamente nesse papel.

```rust
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{Mutex, Semaphore};

struct DominioLimiter {
    semaforos: Mutex<HashMap<String, Arc<Semaphore>>>,
    max_por_dominio: usize,
}

impl DominioLimiter {
    fn novo(max_por_dominio: usize) -> Self {
        Self { semaforos: Mutex::new(HashMap::new()), max_por_dominio }
    }

    async fn acquire(&self, dominio: &str) -> tokio::sync::OwnedSemaphorePermit {
        let sem = {
            let mut mapa = self.semaforos.lock().await;
            mapa.entry(dominio.to_string())
                .or_insert_with(|| Arc::new(Semaphore::new(self.max_por_dominio)))
                .clone()
        };
        sem.acquire_owned().await.unwrap()
    }
}
```

Esse padrão mantém a concorrência global alta sem esmagar servidores individuais. É a diferença entre um crawler que escala e um que vira ruído na infraestrutura alheia.

## Respeito a robots.txt e boas práticas éticas

Crawling é uma atividade pública, mas isso não significa livre de responsabilidade. Antes de qualquer coleta em escala, leia e respeite o `/robots.txt` do domínio. O arquivo declara quais caminhos cada `User-agent` pode visitar e, frequentemente, um `Crawl-delay` que recomenda um intervalo mínimo entre requisições. O crate `robots_txt` faz o parsing correto e deve ser consultado sempre que um novo domínio entra na fronteira.

Outras práticas que distinguem um crawler profissional de um script apressado:

- **Identificação clara**: use um user-agent com nome do projeto e contato.
- **Rate limit explícito**: nunca confie apenas no semáforo. Adicione um atraso entre requisições por domínio, ajustado ao `Crawl-delay` ou a um mínimo conservador.
- **Timeouts realistas**: defina tempos máximos de conexão e de resposta para evitar tarefas presas em servidores lentos.
- **Retentativa com backoff**: erre em 429 e 5xx com espera exponencial, mas evite retentar 4xx definitivos.
- **Cabeçalhos condicionais**: use `ETag` e `If-Modified-Since` para pular páginas que não mudaram.
- **Profundidade e priorização**: nem toda URL merece a mesma urgência. Defina profundidade máxima e priorize por domínio e por relevância.

Coleta de dados públicos não precisa ser agressiva para ser útil. Quem mantém um crawler por meses aprende que servidores bem tratados raramente bloqueiam.

## Quando um navegador headless é necessário

A maior parte do conteúdo ainda vem em HTML estático, mas cada vez mais sites renderizam tudo em JavaScript. Nesses casos, `reqwest` baixa um shell vazio e o `scraper` não encontra os elementos esperados. A solução é executar um navegador controlado por uma API de automação.

Em Rust, a opção madura para automação com WebDriver é `fantoccini`, que conversa com ChromeDriver ou GeckoDriver. Outra alternativa é `chromiumoxide`, mais integrado ao CDP (Chrome DevTools Protocol). O custo é maior: cada página abre um processo de navegador, consome memória e demora mais para carregar.

```rust
use fantoccini::{Client, ClientBuilder, Locator};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cliente = ClientBuilder::native()
        .connect("http://localhost:4444")
        .await?;

    cliente.goto("https://example.com/busca?q=rust").await?;
    cliente
        .wait_for_find(Locator::Css(".resultado__item"))
        .await?;

    let html = cliente.source().await?;
    let documento = scraper::Html::parse_document(&html);
    // ... seletores normais do scraper ...

    cliente.close().await?;
    Ok(())
}
```

A recomendação prática é usar navegador headless apenas quando estritamente necessário e limitar o escopo à renderização inicial. Assim que o HTML estiver disponível, transfira o parsing para `scraper`, mais leve e rápido, e feche a página. Híbridos assim mantêm o custo controlável.

## Erros, retentativas e isolamento por domínio

Coleta em escala encontra todo tipo de falha: DNS, timeout, TLS expirado, redirect infinito, HTML malformado, mudança de layout, bloqueio por IP e queda de rede. Um crawler robusto precisa tratar cada falha como um evento normal, não como exceção.

A combinação de [`anyhow` e `thiserror`](/ecossistema/anyhow-thiserror/) resolve bem a fronteira de erro. Use `thiserror` para os erros esperados do domínio (URL inválida, robots proibindo, schema desconhecido) e `anyhow` para orquestrar handlers. Registre tudo com [`tracing`](/ecossistema/tracing/), com campos estruturados por URL e domínio, para depurar depois.

Importante: nunca deixe uma falha em um domínio derrubar o restante. Cada tarefa de coleta deve ser isolada, com timeout próprio e captura de erro na fronteira. Um domínio que falha dez vezes seguidas pode ser marcado como `pausado` por algumas horas e reavaliado depois. Esse tipo de circuit breaker é o que mantém um crawler rodando por semanas sem intervenção manual.

## Proxies, sessões e autenticação

Em alguns cenários, coleta legítima passa por proxies. Pode ser para distribuir carga, acessar conteúdo regional ou respeitar limites por IP. O `reqwest` aceita proxies HTTP, HTTPS e SOCKS5 e permite rotação por requisição.

Sessões autenticadas exigem cuidado extra. Cookies devem ser armazenados em um `cookie_store` do `reqwest`, e cabeçalhos como `Authorization` precisam ser renovados conforme expiram. Para qualquer coleta que envolva login, verifique sempre os termos de serviço do site e prefira APIs oficiais quando existirem. Boa parte dos bloqueios que parecem arbitrários vem de comportamentos que o site já documenta como proibidos.

## Quando vale a pena sair de Python e escrever em Rust

Muitos pipelines de dados começam em Python com Scrapy, BeautifulSoup e requests. Para volumes moderados, esse stack é excelente. A migração para Rust faz sentido quando três condições aparecem juntas: o volume de páginas por dia passa da casa das centenas de milhares, a latência por requisição importa para o custo total e a estabilidade de execução prolongada vira prioridade.

Rust não substitui a velocidade de prototipação do Python, mas brilha em consumo previsível de CPU e memória. Um crawler Rust de produção pode rodar em uma máquina menor, manter dezenas de milhares de conexões controladas e passar dias sem reiniciar. Para quem já decidiu aprender Rust e busca um projeto onde a linguagem aparece inteira — async, concorrência, erros, configuração, observabilidade —, um crawler é um dos melhores exercícios possíveis, com valor direto para a [carreira Rust](/carreira/) e portfólio real.

## Conclusão

Web crawling e scraping em Rust em 2026 são realidade madura. A combinação de reqwest, scraper, Tokio e, quando necessário, fantoccini cobre praticamente todo o espectro de coleta de dados, do site estático à aplicação renderizada em JavaScript. A vantagem real não é apenas velocidade: é a possibilidade de construir pipelines que rodam por semanas, respeitam o lado do servidor e produzem dados limpos com erros tratados explicitamente.

Quem está começando pode replicar o [projeto web crawler](/projetos/crawler/) e evoluir para um scraper estruturado. Quem já trabalha com pipelines de dados encontra em Rust uma base estável para crescer sem reescrever a cada mudança de volume — algo que empresas brasileiras que usam Rust para ingestão de dados já descobriram na prática. Em um mercado que paga bem e busca engenheiros capazes de operar sistemas distribuídos com segurança, dominar essa stack é um diferencial concreto.
