Web Scraping & Agentes

Como corrigir erros HTTP 429 e Cloudflare 520 em rastreadores e agentes de IA

Resposta rápida: O erro HTTP 429 indica limite de taxa (rate limit) na API de destino ou WAF, resolvido com backoff de jitter descorrelacionado (Decorrelated Jitter) e pools de proxies residenciais rotativos. O erro Cloudflare 520 («Web Server Returned an Unknown Error») surge quando o proxy perimetral recebe respostas corrompidas, resets TCP RST ou cabeçalhos excessivos do servidor de origem durante picos de rastreamento.

1. Introdução: A vulnerabilidade dos rastreadores de IA autônomos

À medida que os agentes autônomos de IA evoluem de interfaces de chat para motores de raciocínio de múltiplas etapas (pesquisa web em tempo real, auditorias de mercado, extração de código), o gargalo central não é mais o processamento do LLM, mas sim a confiabilidade da rede e a conectividade no Edge.

Sistemas modernos em produção disparam milhares de requisições HTTP por minuto. Diferente dos rastreadores legados (Scrapy, Googlebot), os agentes de IA exigem:

  1. Recuperação síncrona com baixa latência para não travar o loop de raciocínio.
  2. Execução profunda de JavaScript para lidar com aplicações SPA.
  3. Conversão limpa para Markdown eliminando tokens desnecessários.

Contudo, defesas perimetrais como Cloudflare e Akamai bloqueiam o tráfego de agentes por meio de dois erros recorrentes:

  • HTTP 429 Too Many Requests: Limite de chamadas excedido.
  • HTTP 520 Web Server Returned an Unknown Error (Cloudflare): Falha de comunicação entre a borda da Cloudflare e o servidor de origem sob alta carga de crawling.

2. Anatomia do HTTP 429: Limitação de taxa e impressões digitais

Definido pela RFC 6585, o erro 429 ocorre em três camadas:

  1. Camada 1: WAF de borda (Cloudflare / DataDome): Identifica a divergência entre o fingerprint TLS JA4 do cliente e o User-Agent enviado, emitindo um 429 preventivo.
  2. Camada 2: API Gateway (Kong, Envoy, Nginx): Algoritmos de Token Bucket e Leaky Bucket.
  3. Camada 3: Servidor de origem: Proteção contra exaustão de conexões e CPU.

Cabeçalhos cruciais para análise: Retry-After, RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset.


3. Desmistificando o erro 520 da Cloudflare

O status 520 é exclusivo da Cloudflare e indica uma falha na infraestrutura de origem:

  1. Queda de processos de backend (OOM / SIGSEGV): Conexões concorrentes massivas geram esgotamento de memória, resultando em envio de pacotes TCP RST para a Cloudflare.
  2. Dessincronização de Keep-Alive: A Cloudflare mantém conexões ativas por 15s. Se o servidor Nginx fecha conexões em 5s, as requisições em trânsito são abortadas com erro 520.
  3. Cabeçalhos excessivos (>16 KB): Loops de cookies ou logs de depuração excedem o limite de buffer da borda.
  4. Respostas vazias (Zero Bytes): O servidor encerra a conexão sem enviar dados.

4. Remediação algorítmica: Backoff exponencial, Jitter e Circuit Breaker

Tentativas cegas de repetição (sleep(1)) criam o efeito de estouro em manada (Thundering Herd). A melhor prática segundo a AWS é o Jitter Descorrelacionado (Decorrelated Jitter):

# Fórmula de Jitter Descorrelacionado:
sleep = min(cap, random.uniform(base, previous_delay * 3.0))

Implementação Python resiliente com Circuit Breaker

import asyncio, random, time, aiohttp
from urllib.parse import urlparse

class ResilientAgentCrawler:
    def __init__(self, base_delay=1.0, max_delay=60.0, max_retries=5, threshold=4, cooldown=30.0):
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.max_retries = max_retries
        self.threshold = threshold
        self.cooldown = cooldown
        self.failures = {}
        self.opened_at = {}

    def _is_open(self, domain):
        t = self.opened_at.get(domain)
        if not t: return False
        if time.monotonic() - t > self.cooldown:
            del self.opened_at[domain]
            self.failures[domain] = 0
            return False
        return True

    async def fetch(self, session, url):
        domain = urlparse(url).netloc
        if self._is_open(domain):
            raise RuntimeError(f"Circuit Breaker ativo para {domain}")

        delay = self.base_delay
        for attempt in range(1, self.max_retries + 1):
            try:
                async with session.get(url) as resp:
                    if resp.status == 200:
                        self.failures[domain] = 0
                        return await resp.text()
                    elif resp.status == 429:
                        retry_after = resp.headers.get("Retry-After")
                        wait = float(retry_after) if retry_after else random.uniform(self.base_delay, delay * 3.0)
                        delay = min(self.max_delay, wait)
                        await asyncio.sleep(delay)
                    elif resp.status in (520, 502, 503, 504):
                        self.failures[domain] = self.failures.get(domain, 0) + 1
                        if self.failures[domain] >= self.threshold:
                            self.opened_at[domain] = time.monotonic()
                        delay = min(self.max_delay, random.uniform(self.base_delay, delay * 3.0))
                        await asyncio.sleep(delay)
                    else:
                        resp.raise_for_status()
            except Exception as e:
                if attempt == self.max_retries: raise e
                await asyncio.sleep(delay)

5. Emulação TLS com curl_cffi

Bibliotecas padrão de Python são detectadas pela assinatura JA4 da OpenSSL. Use curl_cffi para simular navegadores Chrome com exatidão:

from curl_cffi.requests import AsyncSession

async def fetch_stealth(url: str):
    async with AsyncSession(impersonate="chrome124") as s:
        res = await s.get(url, timeout=15)
        return res.text

6. Arquitetura de proxies e ajustes no Nginx

  • Proxies residenciais rotativos: Dividem a carga em milhares de nós reais.
  • Proxies móveis (4G/5G com CGNAT): Tolerância máxima em WAFs devido ao compartilhamento de IP entre múltiplos usuários.
  • Ajustes no Nginx para evitar o erro 520:
http {
    keepalive_timeout 75s;
    keepalive_requests 10000;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
}

7. Conclusão

Para blindar seus rastreadores de IA:

  1. Utilize Decorrelated Jitter para mitigar concorrência nos reintentos.
  2. Empregue curl_cffi para imitar o comportamento de rede e TLS de navegadores reais.
  3. Ajuste o Keep-Alive dos servidores de origem para 75 segundos.
  4. Configure Circuit Breakers para evitar quebra de orçamento em tokens LLM.
← Todos os artigos
0 / 4