---
title: "O que é Crawl4AI? Instalação e configuração de proxy"
description: "O Crawl4AI, crawler Python de código aberto, converte páginas em Markdown pronto para LLMs. Veja a instalação via pip e Docker e o proxy com ProxyConfig."
url: https://proxynet.io/pt-br/blog/crawl4ai-proxy
date: 2026-09-24
author: "Acar Diveroli"
category: "IA, Integração"
lang: pt-BR
---

# O que é Crawl4AI? Instalação e configuração de proxy

Uma equipe quer que o seu assistente interno responda a perguntas com base na documentação do produto. Ela baixa as páginas com Requests e entrega o HTML ao modelo, mas menus, banners de cookies e scripts ocupam metade do texto, e as páginas que carregam o conteúdo com JavaScript chegam quase vazias. O Crawl4AI abre as mesmas páginas em um navegador real e devolve o corpo como Markdown limpo. No segundo dia, os problemas mudam: na metade de uma coleta de 300 páginas, o site começa a responder `429`, e a instalação com Docker no servidor de build devolve só "connection reset".

Este guia mostra como o Crawl4AI transforma uma página em Markdown, a instalação via pip e Docker, os proxies, o uso rotativo e sticky e as configurações de robots.txt e de ritmo que mantêm a coleta respeitosa com o site. Executamos todos os exemplos em Python com Crawl4AI 0.9.4 e Python 3.13, por meio de um proxy local de teste com usuário e senha. O Docker não estava disponível na máquina de teste, então os comandos de Docker seguem o guia oficial.

> **Nota: Resposta rápida**
>
> O Crawl4AI é uma biblioteca Python de código aberto (Apache 2.0) que abre páginas web em um navegador real por meio do Playwright e as transforma em Markdown pronto para um modelo de linguagem. Instale com `pip install -U crawl4ai` e `crawl4ai-setup`, ou rode o servidor Docker na porta 11235, que exige um `CRAWL4AI_API_TOKEN` desde a 0.9.0. Um proxy é um `ProxyConfig(server, username, password)` passado como `proxy_config` ao `CrawlerRunConfig` ou ao `BrowserConfig`. Um gateway rotativo precisa de um único `ProxyConfig`; o `RoundRobinProxyStrategy` serve para uma lista fixa de IPs. A API do Docker rejeita proxy na requisição, então use o SDK para o trabalho com proxy.

## O que é o Crawl4AI e para que ele serve?

O Crawl4AI é uma biblioteca Python de código aberto que busca páginas web e devolve o conteúdo delas em uma forma que um modelo de linguagem consegue ler. Ele controla o Chromium por meio do Playwright, então as páginas montadas com JavaScript são renderizadas antes de serem lidas ([páginas estáticas e dinâmicas](/pt-br/blog/static-vs-dynamic-pages)). Uma única coleta devolve a página como Markdown, um Markdown "fit" mais curto, sem menus e rodapés, os links, a lista de mídia e, quando solicitado, uma captura de tela ou um PDF. Com uma estratégia de extração, ele também pode preencher um schema JSON.

A biblioteca exige Python 3.10 ou mais recente; a versão 0.9.4 saiu no [PyPI](https://pypi.org/project/Crawl4AI/) em 23 de setembro de 2026. Você pode usá-la como SDK Python, como servidor Docker com uma API REST e um endpoint MCP, ou pela ferramenta de linha de comando `crwl`.

O Crawl4AI é antes de tudo um crawler: ele visita páginas e segue links, e o que você extrai fica por sua conta ([web crawling e web scraping](/pt-br/blog/web-scraping-vs-web-crawling)). A comparação com as ferramentas de scraping com IA em geral está em [Como funciona um AI web scraper](/pt-br/blog/ai-web-scraper-how-it-works-2026).

## Como o Crawl4AI transforma uma página em Markdown?

Uma chamada a `arun()` passa por estas etapas:

1. **O navegador inicia** com as configurações do `BrowserConfig`: modo headless, o `User-Agent` e, se definido ali, um proxy para o navegador inteiro.
2. **O robots.txt é verificado** se o `CrawlerRunConfig` tiver `check_robots_txt=True`. Uma URL proibida nunca é aberta; o resultado vem com status `403` e "Access denied by robots.txt".
3. **A página carrega** no Chromium, pelo proxy da execução, se houver um, e o JavaScript dela roda.
4. **O HTML é limpo:** scripts e estilos são descartados, links e mídia são coletados.
5. **O `DefaultMarkdownGenerator` escreve o `raw_markdown`.**
6. **Um `content_filter` escreve o `fit_markdown`:** o `PruningContentFilterLXML` mantém os blocos com mais texto, e o `BM25ContentFilter`, os blocos que correspondem a uma consulta. Sem filtro, o `fit_markdown` fica vazio.
7. **Um `CrawlResult` volta** com `success`, `status_code`, `error_message`, `markdown` e `links`.

Em uma das nossas páginas de teste, o Markdown bruto tinha 1.241 caracteres e o fit Markdown, 712: o menu e o rodapé saíram, o artigo ficou. Um aviso de cookies permaneceu, porque o filtro pontua a densidade de texto e de links, não o significado; `excluded_selector=".cookie"` no `CrawlerRunConfig` o removeu.

## Como instalar o Crawl4AI com pip ou Docker?

O caminho via pip instala a biblioteca e uma build do Chromium. O caminho via Docker inicia um servidor que outros programas chamam por HTTP.

```bash
# SDK Python
pip install -U crawl4ai
crawl4ai-setup      # instala o navegador do Playwright que o Crawl4AI usa
crawl4ai-doctor     # roda uma coleta de teste para verificar a instalação

# Servidor Docker: a 0.9.0 e as versões seguintes exigem um token
export CRAWL4AI_API_TOKEN="$(openssl rand -hex 32)"
docker run -d -p 11235:11235 --name crawl4ai --shm-size=1g \
  -e CRAWL4AI_API_TOKEN="$CRAWL4AI_API_TOKEN" \
  unclecode/crawl4ai:0.9.4

curl http://localhost:11235/health    # responde sem token
curl -X POST http://localhost:11235/md \
  -H "Authorization: Bearer $CRAWL4AI_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://quotes.toscrape.com/", "f": "fit"}'
```

O [guia de self-hosting](https://docs.crawl4ai.com/core/self-hosting/) usa a tag `latest`; uma tag de versão evita que uma atualização da imagem mude o comportamento do servidor sem que você perceba. As páginas `/playground` e `/dashboard` têm um campo para o token no topo.

> **Atenção: Connection reset depois do docker run**
>
> Guias escritos antes da 0.9.0, e o quick start do README no dia em que conferimos, iniciam o contêiner sem token. A partir da 0.9.0, um servidor assim escuta apenas no endereço de loopback do próprio contêiner, então a porta publicada responde "connection reset", embora o `docker ps` mostre um contêiner saudável. A forma curta `-e CRAWL4AI_API_TOKEN` sem valor faz o mesmo quando a variável não está definida no seu shell.

As três formas de rodar o Crawl4AI diferem sobretudo no lugar onde o proxy pode ficar:

| Forma | Instalação | Onde fica o proxy | robots.txt e ritmo | Indicada para |
|---|---|---|---|---|
| SDK Python | `pip install`, `crawl4ai-setup` | `proxy_config` no `CrawlerRunConfig` ou no `BrowserConfig`; `proxy_rotation_strategy` para uma lista | `check_robots_txt`, `SemaphoreDispatcher`, `RateLimiter` | Qualquer trabalho que precise do seu próprio proxy |
| Servidor Docker | `docker run` com token, porta 11235 | Não na requisição (HTTP 400) | `check_robots_txt` permitido na requisição | Chamadas de outras linguagens, do n8n ou de agentes |
| CLI `crwl` | Vem com o pip | Arquivo de configuração do navegador, `-B` | Arquivo de configuração do crawler, `-C` | Uma página para Markdown |

## Como obter Markdown na sua primeira coleta em Python?

O script abre uma página por um proxy, verifica o robots.txt primeiro e imprime o tamanho das duas versões de Markdown. O endereço do proxy vem de uma variável de ambiente, então a senha fica fora do código.

```python
"""Coleta uma página por um proxy e imprime o Markdown dela."""
import asyncio
import os
import sys

from crawl4ai import (
    AsyncWebCrawler,
    BrowserConfig,
    CacheMode,
    CrawlerRunConfig,
    DefaultMarkdownGenerator,
    ProxyConfig,
    PruningContentFilterLXML,
)

URL = sys.argv[1] if len(sys.argv) > 1 else "https://quotes.toscrape.com/"

async def main():
    # PROXY_URL=http://user:pass@pr.proxynet.io:8000, mantido fora do código
    proxy = ProxyConfig.from_string(os.environ["PROXY_URL"])

    browser_config = BrowserConfig(
        headless=True,
        user_agent="NorthwindDocsBot/1.0 (+https://example.com/bot)",
    )
    run_config = CrawlerRunConfig(
        proxy_config=proxy,
        check_robots_txt=True,
        cache_mode=CacheMode.BYPASS,
        markdown_generator=DefaultMarkdownGenerator(
            content_filter=PruningContentFilterLXML(threshold=0.48)
        ),
    )

    async with AsyncWebCrawler(config=browser_config) as crawler:
        result = await crawler.arun(URL, config=run_config)

    if not result.success:
        print(f"failed: {result.status_code} {result.error_message}")
        return

    md = result.markdown
    print(f"status {result.status_code}")
    print(f"raw_markdown: {len(md.raw_markdown)} characters")
    print(f"fit_markdown: {len(md.fit_markdown)} characters")
    print(md.fit_markdown[:400])

asyncio.run(main())
```

No quotes.toscrape.com, um site de prática para scraping, ele imprimiu:

```text
status 200
raw_markdown: 4375 characters
fit_markdown: 3663 characters
```

`CacheMode.BYPASS` busca a página toda vez; sem ele, as URLs repetidas vêm do cache local. Use o `PruningContentFilterLXML`: na 0.9.4, o antigo `PruningContentFilter` imprime um aviso de descontinuação (deprecation warning).

## Como configurar um proxy no Crawl4AI?

Um proxy é um `ProxyConfig` com `server`, `username` e `password`, e ele vai para um de dois lugares:

```python
from crawl4ai import BrowserConfig, CrawlerRunConfig, ProxyConfig

proxy = ProxyConfig(server="http://pr.proxynet.io:8000", username="user", password="pass")

run_config = CrawlerRunConfig(proxy_config=proxy)    # só esta execução
browser_config = BrowserConfig(proxy_config=proxy)   # toda página que este navegador abre
```

O [guia oficial de proxy](https://docs.crawl4ai.com/advanced/proxy-security/) recomenda o `CrawlerRunConfig`, para que cada execução leve o próprio proxy. As duas formas funcionaram no nosso teste.

O `ProxyConfig.from_string()` lê `http://user:pass@host:port`, `host:port:user:pass`, `host:port` e `socks5://host:port`. O `ProxyConfig.from_env("PROXIES")` lê uma lista separada por vírgulas de uma variável de ambiente. O antigo parâmetro `proxy=` ainda funciona, mas imprime um aviso de descontinuação.

**SOCKS5 com senha não funciona.** Com `socks5://` e um usuário, em qualquer uma das duas formas, a nossa coleta falhou com "Browser does not support socks5 proxy authentication". O limite é do Chromium ([Playwright com proxy](/pt-br/blog/playwright-proxy)). Use o endpoint HTTP do proxy ou libere o IP do seu servidor no painel do proxy (whitelist de IP) e conecte sem senha ([SOCKS e HTTP proxy](/pt-br/blog/socks-vs-http-proxy)).

Para verificar o proxy, faça a coleta de uma página que mostra o IP do visitante.

## Rotativo ou sticky: quando você precisa do RoundRobinProxyStrategy?

Depende de para onde aponta o endereço do seu proxy.

**Um gateway rotativo** é um único endereço, como `pr.proxynet.io:8000`, atrás do qual o provedor troca o IP de saída. Com os [Proxies rotativos](https://proxynet.io/pt-br/rotating-proxy) ou com os [Proxies residenciais](https://proxynet.io/pt-br/residential-proxy) em modo rotativo, um único `ProxyConfig` é tudo de que o Crawl4AI precisa. A demonstração da página oficial de proxy compara o IP que o site viu com `ProxyConfig.ip`; com um gateway, essa verificação sempre acusa divergência, porque o IP de saída nunca é o endereço do gateway.

**Uma lista fixa de IPs**, por exemplo de um plano de [Proxies de datacenter](https://proxynet.io/pt-br/datacenter-proxy) ou de [Proxies ISP](https://proxynet.io/pt-br/static-isp-residential-proxy), é onde o `RoundRobinProxyStrategy` ajuda: cada requisição recebe o próximo proxy da lista. Com `proxy_session_id`, as requisições com o mesmo ID mantêm o mesmo proxy até passarem `proxy_session_ttl` segundos:

```python
"""Alterna entre uma lista fixa de proxies ou mantém um deles durante uma sessão inteira."""
import asyncio

from crawl4ai import (
    AsyncWebCrawler,
    BrowserConfig,
    CacheMode,
    CrawlerRunConfig,
    ProxyConfig,
    RoundRobinProxyStrategy,
)

# PROXIES="http://user:pass@203.0.113.10:8000,http://user:pass@203.0.113.11:8000"
strategy = RoundRobinProxyStrategy(ProxyConfig.from_env("PROXIES"))

rotate = CrawlerRunConfig(proxy_rotation_strategy=strategy, cache_mode=CacheMode.BYPASS)
sticky = CrawlerRunConfig(
    proxy_rotation_strategy=strategy,
    proxy_session_id="catalog-1",  # toda requisição com este id recebe o mesmo proxy
    proxy_session_ttl=600,         # segundos; depois disso a sessão escolhe outro
    cache_mode=CacheMode.BYPASS,
)

async def main(base):
    async with AsyncWebCrawler(config=BrowserConfig(headless=True)) as crawler:
        for label, config in (("rotate", rotate), ("sticky", sticky)):
            for page in range(1, 4):
                result = await crawler.arun(f"{base}/catalog?page={page}", config=config)
                print(label, page, result.status_code, config.proxy_config.server)

asyncio.run(main("https://shop.example.com"))
```

Com dois proxies locais, as requisições `rotate` se alternaram e as `sticky` mantiveram um único proxy. Os parâmetros de sessão estão no código-fonte da 0.9.4, mas não na página de documentação de proxy, então confira-os de novo depois de cada atualização.

Mantenha as duas camadas separadas. A sessão sticky do Crawl4AI escolhe a mesma entrada da sua lista; atrás de um gateway rotativo, o IP de saída só fica fixo se o provedor o segurar, como fazem os [Proxies de sessão fixa](https://proxynet.io/pt-br/sticky-proxy) por 1 a 60 minutos. Os modos estão explicados em [rotação de IP](/pt-br/blog/ip-rotation-explained), e a rotação para clientes HTTP simples, em [como rotacionar proxies em Python](/pt-br/blog/how-to-rotate-proxies-in-python).

## Por que uma requisição ao Docker não pode levar um proxy?

Desde a 0.9.0, o servidor Docker é seguro por padrão. Um corpo de requisição com `proxy` ou `proxy_config` recebe HTTP 400, assim como `js_code`, `headers`, `cookies`, `magic` e vários outros campos ([notas de migração da 0.9.0](https://github.com/unclecode/crawl4ai/blob/main/deploy/docker/MIGRATION.md)). O motivo é o server-side request forgery (SSRF): sem esse bloqueio, quem chama a API poderia mandar o navegador do servidor por qualquer proxy ou para endereços internos.

As notas dizem para configurar essas opções no servidor, mas no código-fonte da 0.9.4 um guarda de saída (egress guard) remove qualquer `proxy_config`, inclusive um definido no `config.yml`, e passa o Chromium pelo proxy de filtragem do próprio servidor. O código-fonte também lê um proxy HTTP upstream de `CRAWL4AI_UPSTREAM_PROXY` ou `HTTPS_PROXY`; isso não está documentado, e não conseguimos testar. Para usar o seu próprio proxy, rode o SDK no seu próprio serviço.

## Como configurar robots.txt, ritmo e concorrência?

`check_robots_txt` é `False` por padrão. Testamos o comportamento da 0.9.4 contra sites locais:

- **Um caminho proibido** devolve status `403`, e a página nunca é requisitada.
- **Um robots.txt que responde `500`** conta como "tudo permitido", assim como um timeout de 2 segundos e um erro de rede. A [RFC 9309](https://www.rfc-editor.org/rfc/rfc9309.html) diz que, em erros de servidor, o crawler deve presumir proibição total.
- **As regras ficam em cache por 7 dias.** A RFC 9309 diz que uma cópia em cache não deve ser usada por mais de 24 horas; `crawler.robots_parser.clear_cache()` esvazia o cache.
- **O robots.txt é buscado direto da sua máquina**, não pelo proxy, com um `User-Agent` genérico do `aiohttp`.
- **As regras são comparadas com o `BrowserConfig.user_agent`.** O padrão é uma string do Chrome, então um `Disallow` para o nome do seu bot só vale quando o seu `User-Agent` traz esse nome. Um caminho sob `/private` abriu com a string padrão e devolveu `403` com `NorthwindDocsBot/1.0`.

Para trabalhos sensíveis, verifique o robots.txt você mesmo antes ([robots.txt](/pt-br/blog/robots-txt), [O que é User-Agent?](/pt-br/blog/what-is-user-agent)).

O ritmo é definido no dispatcher. `SemaphoreDispatcher(semaphore_count=3)` mantém no máximo três páginas abertas ao mesmo tempo ([concorrência e paralelismo](/pt-br/blog/concurrency-vs-parallelism)). O `RateLimiter` espera entre as requisições a um mesmo domínio, mais ou menos dobra a espera depois de um `429` ou `503`, até o `max_delay`, e a encurta depois de sucessos. Ele não busca de novo uma página recusada: o resultado volta com `429`, e a nova tentativa fica por sua conta. O script abaixo faz a coleta em lotes de três e dá às páginas recusadas mais duas rodadas:

```python
"""Coleta as páginas de um site em ritmo respeitoso e salva cada uma como Markdown."""
import asyncio
import os
import re
from pathlib import Path

from crawl4ai import (
    AsyncWebCrawler,
    BrowserConfig,
    CacheMode,
    CrawlerRunConfig,
    DefaultMarkdownGenerator,
    ProxyConfig,
    PruningContentFilterLXML,
    RateLimiter,
    SemaphoreDispatcher,
)

BASE = os.environ.get("DOCS_BASE", "https://docs.example.com")
URLS = [f"{BASE}/docs/{n}" for n in range(1, 13)] + [f"{BASE}/private/report"]
OUT = Path("pages")
BATCH = 3                 # páginas abertas ao mesmo tempo
PAUSE = 5.0               # segundos entre dois lotes
RETRY_CODES = {429, 503}  # valem outra tentativa mais tarde
ROUNDS = 3                # primeira passada mais duas rodadas de nova tentativa
ROUND_PAUSE = 60          # segundos antes de uma rodada de nova tentativa; dobra a cada rodada

def file_name(url):
    return re.sub(r"[^a-z0-9]+", "-", url.lower()).strip("-") + ".md"

async def crawl_round(crawler, urls, run_config, dispatcher):
    """Coleta as urls em lotes pequenos e devolve as que devem ser tentadas de novo depois."""
    retry = []
    for i in range(0, len(urls), BATCH):
        results = await crawler.arun_many(
            urls[i : i + BATCH], config=run_config, dispatcher=dispatcher
        )
        for r in results:
            if r.success and r.status_code == 200:
                (OUT / file_name(r.url)).write_text(r.markdown.fit_markdown, encoding="utf-8")
                print(f"saved  {r.url}")
            elif r.status_code in RETRY_CODES:
                retry.append(r.url)
                print(f"later  {r.status_code} {r.url}")
            else:
                print(f"skip   {r.status_code} {r.url}: {r.error_message}")
        await asyncio.sleep(PAUSE)
    return retry

async def main():
    OUT.mkdir(exist_ok=True)
    browser_config = BrowserConfig(
        headless=True,
        user_agent="NorthwindDocsBot/1.0 (+https://example.com/bot)",
    )
    run_config = CrawlerRunConfig(
        proxy_config=ProxyConfig.from_string(os.environ["PROXY_URL"]),
        check_robots_txt=True,
        cache_mode=CacheMode.BYPASS,
        page_timeout=30000,
        markdown_generator=DefaultMarkdownGenerator(
            content_filter=PruningContentFilterLXML(threshold=0.48)
        ),
    )
    # Um único dispatcher para a execução inteira: o RateLimiter mantém o ritmo mais lento que aprendeu com os 429
    dispatcher = SemaphoreDispatcher(
        semaphore_count=BATCH,
        rate_limiter=RateLimiter(base_delay=(1.0, 3.0), max_delay=60.0, max_retries=3),
    )

    pending = list(URLS)
    async with AsyncWebCrawler(config=browser_config) as crawler:
        for round_no in range(ROUNDS):
            if round_no:
                wait = ROUND_PAUSE * 2 ** (round_no - 1)
                print(f"round {round_no + 1}: {len(pending)} pages again in {wait} s")
                await asyncio.sleep(wait)
            pending = await crawl_round(crawler, pending, run_config, dispatcher)
            if not pending:
                break

    print(f"done, {len(pending)} pages still refused")

asyncio.run(main())
```

Apontamos `DOCS_BASE` para um site local que responde `429` a cada quarta requisição sob `/docs/` e proíbe `/private`, e definimos `ROUND_PAUSE` como 5 segundos para o teste. Saída resumida:

```text
saved  http://192.168.1.2:28130/docs/1
saved  http://192.168.1.2:28130/docs/2
saved  http://192.168.1.2:28130/docs/3
later  429 http://192.168.1.2:28130/docs/4
...
later  429 http://192.168.1.2:28130/docs/11
saved  http://192.168.1.2:28130/docs/12
skip   403 http://192.168.1.2:28130/private/report: Access denied by robots.txt
round 2: 3 pages again in 5 s
saved  http://192.168.1.2:28130/docs/4
saved  http://192.168.1.2:28130/docs/9
saved  http://192.168.1.2:28130/docs/11
done, 0 pages still refused
```

O próprio log do Crawl4AI também imprime "Blocked by anti-bot protection: HTTP 429 Too Many Requests" para cada página recusada. Como tratar um cabeçalho `Retry-After` de verdade está explicado em [códigos de status HTTP no web scraping](/pt-br/blog/http-status-codes-web-scraping).

## Qual é a diferença entre Crawl4AI e Firecrawl?

Os dois transformam páginas em Markdown para modelos de linguagem; a diferença está na forma de rodá-los.

| | Crawl4AI | Firecrawl |
|---|---|---|
| Licença | Apache 2.0 mais uma exigência de atribuição | AGPL-3.0 |
| Forma principal | Uma biblioteca Python; servidor Docker opcional | Uma API hospedada; self-hosting possível |
| Partes no self-hosting | Um contêiner | API, workers, Playwright, Redis, RabbitMQ, PostgreSQL |
| Custo | O seu servidor, o proxy e qualquer LLM | Plano da API ou os seus próprios servidores |

O [guia de self-hosting](https://github.com/firecrawl/firecrawl/blob/main/SELF_HOST.md) do Firecrawl observa que a API self-hosted não tem autenticação por padrão. O Crawl4AI se encaixa em uma equipe Python que quer rodar as próprias coletas e os próprios proxies.

## Como usar o Crawl4AI com MCP e n8n?

O servidor Docker expõe MCP em `/mcp/sse` e `/mcp/ws`, com as ferramentas `md`, `html`, `screenshot`, `pdf`, `execute_js`, `crawl` e `ask`. O comando do guia para o Claude Code não tem token, mas os endpoints MCP ficam atrás da mesma checagem de token que a API, então adicione o cabeçalho:

```bash
claude mcp add --transport sse c4ai-sse http://localhost:11235/mcp/sse \
  --header "Authorization: Bearer $CRAWL4AI_API_TOKEN"
```

Clientes WebSocket que não conseguem definir cabeçalhos podem passar `?token=`. O protocolo está explicado em [O que é MCP?](/pt-br/blog/what-is-mcp), e como dar a um agente um navegador completo, em [Playwright MCP](/pt-br/blog/playwright-mcp).

No n8n, um nó HTTP Request envia `POST /md` com o cabeçalho Bearer e um corpo como `{"url": "https://quotes.toscrape.com/", "f": "fit"}`; a página volta no campo `markdown` ([web scraping com n8n](/pt-br/blog/n8n-proxy)).

## Casos de uso

- **Documentação para RAG:** a documentação do produto como Markdown para um índice de busca, atrás das checagens de [acesso seguro à web para LLMs](/pt-br/blog/llm-safe-web-access).
- **Entrada limpa para agentes:** fit Markdown em vez de HTML bruto ([agentic web scraping](/pt-br/blog/agentic-web-scraping-how-it-works-2026)).
- **Checagem de preços:** páginas de produto transformadas em JSON com um schema CSS ([monitoramento de preços](/pt-br/price-monitoring)).
- **Inventário do seu próprio site:** todas as páginas e links, para auditorias de conteúdo e links quebrados ([web crawler](/pt-br/web-crawler)).
- **Dados de catálogo:** nomes, especificações e preços de páginas públicas de catálogo ([extração de dados](/pt-br/data-scraping)).

## Erros comuns

- **`docker run` sem token,** ou `-e CRAWL4AI_API_TOKEN` sem valor: "connection reset" vindo de um contêiner que parece saudável.
- **`proxy_config` em uma requisição REST:** o servidor responde `400`.
- **`socks5://` com senha:** o Chromium recusa.
- **Um gateway rotativo listado várias vezes no `RoundRobinProxyStrategy`:** o gateway já faz a rotação.
- **Esperar `fit_markdown` sem um `content_filter`:** ele fica vazio.
- **Supor que o robots.txt é verificado:** a verificação vem desligada por padrão, e um robots.txt que não carrega conta como "permitido".
- **Concorrência alta sem `RateLimiter`:** dez páginas em paralelo em um site pequeno parecem uma rajada, e as respostas `429` vêm em seguida.
- **Trocar de IP para forçar um site que respondeu `429`:** em vez disso, reduza o ritmo ([como funciona a detecção de bots](/pt-br/blog/how-bot-detection-works)).

O modo stealth, o modo "magic" e os recursos de fallback anti-bot da documentação ficam fora deste guia, e não os recomendamos.

## Guia de decisão

| Necessidade | Recomendação |
|---|---|
| Algumas páginas de documentação como texto limpo para um LLM | pip install, `arun()` com `PruningContentFilterLXML` |
| Um IP de saída diferente a cada requisição | Um `ProxyConfig` com um gateway residencial rotativo |
| O mesmo IP ao longo de um fluxo de várias etapas | Uma sessão sticky do provedor, mais `proxy_session_id` para uma lista |
| Uma lista fixa de IPs | `ProxyConfig.from_env("PROXIES")` com `RoundRobinProxyStrategy` |
| Coleta a partir do n8n, de outra linguagem ou de um agente | Um servidor Docker com token; o trabalho com proxy fica no SDK |
| Centenas de páginas sem sobrecarregar o site | Lotes pequenos, `RateLimiter`, `check_robots_txt=True` |
| Nenhuma infraestrutura para manter | Uma API hospedada como o Firecrawl |

## Perguntas frequentes

### O Crawl4AI é gratuito?

Sim, a biblioteca é gratuita sob a licença Apache 2.0. O arquivo LICENSE dela acrescenta a exigência de dar crédito ao projeto em usos públicos, por exemplo em um README ou em uma página "Sobre". Os seus custos são o servidor, o proxy e qualquer modelo de linguagem que você chamar.

### Qual versão do Python o Crawl4AI exige?

Python 3.10 ou mais recente, segundo o PyPI. Testamos a versão 0.9.4 com Python 3.13.

### O Crawl4AI funciona com um LLM local como o Ollama?

A saída em Markdown não precisa de modelo de linguagem. Para a extração com LLM, a documentação mostra `LLMConfig(provider="ollama/llama3.3")` para um modelo local do Ollama, sem chave de API.

### Qual é a diferença entre Crawl4AI e Scrapy?

O Scrapy envia requisições HTTP simples e, por padrão, não executa JavaScript; o Crawl4AI renderiza cada página no Chromium e devolve Markdown. O Scrapy se encaixa em coletas grandes de HTML estático ([Scrapy com proxy](/pt-br/blog/scrapy-proxy)); o Crawl4AI, em páginas que vão para um modelo de linguagem. Em [Como criar um web crawler em Python](/pt-br/blog/python-web-crawler), montamos passo a passo um crawler com fila e limite de profundidade, feito com Requests e BeautifulSoup, sem navegador nem framework.

### Posso usar o Crawl4AI a partir do Node.js ou de outra linguagem?

A biblioteca em si é Python. De outras linguagens, chame a API REST do servidor Docker, por exemplo `POST /md` com o token no cabeçalho.

### O que fazer quando um site bloqueia o Crawl4AI?

Primeiro, reduza o ritmo: menos páginas em paralelo, esperas mais longas e uma pausa depois de cada `429`. Verifique o robots.txt e os termos do site e procure uma API ou um feed oficial. Se o site continuar recusando, pare; o lado jurídico está em [Web scraping é legal?](/pt-br/blog/is-data-web-scraping-legal).

## Em resumo

O Crawl4AI transforma páginas em Markdown que um modelo de linguagem consegue ler. Para o trabalho com proxy, use o SDK: um `ProxyConfig` para um gateway rotativo, `RoundRobinProxyStrategy` para uma lista fixa e nada de senha no SOCKS5. O servidor Docker exige um token e não aceita proxy na requisição. Ative o `check_robots_txt`, envie um `User-Agent` honesto e deixe um `RateLimiter` definir o ritmo. Para saídas em muitos países ou um endereço fixo, veja os nossos [serviços de proxy](/pt-br/proxy).
