Rate limiting — uma das causas mais comuns pelas quais scrapers falham, integrações de API falham e scripts automatizados recebem o status 429 Too Many Requests. O servidor vê muitos pedidos de um único IP — e simplesmente para de responder. Neste artigo, vamos discutir como construir corretamente uma infraestrutura de proxies para contornar os limites de solicitações sem bans e falhas — com exemplos reais de código em Python e Node.js.
O que é rate limiting e por que atrasos comuns não ajudam
Rate limiting (limitação de taxa de solicitações) — é um mecanismo de proteção do servidor que limita o número de solicitações de uma única fonte em um determinado período de tempo. A fonte geralmente é um endereço IP, mas sistemas mais avançados também consideram tokens de autorização, User-Agent, cookies e até padrões de comportamento.
Quando seu script excede o limite, o servidor retorna uma das seguintes respostas:
429 Too Many Requests— status HTTP padrão para rate limiting503 Service Unavailable— às vezes usado em vez de 429403 Forbidden— se o IP já estiver na lista negra- Resposta vazia ou timeout — em caso de bloqueio agressivo
A primeira ideia da maioria dos desenvolvedores é adicionar time.sleep(1) entre as solicitações. Isso funciona apenas em limites muito suaves (por exemplo, 60 solicitações por minuto). Mas os cenários reais são mais complexos:
Limites reais de plataformas populares:
- Twitter/X API (gratuito): 500.000 tweets por mês, mas não mais de 15 solicitações a cada 15 minutos
- Google Search: ~100 solicitações por dia de um único IP sem autenticação
- Wildberries, Ozon: rate limiting agressivo — bloqueio após 30–50 solicitações por minuto
- GitHub API: 60 solicitações/hora sem token, 5000/hora com token
- Sites protegidos pelo Cloudflare: podem bloquear após 10–20 solicitações por minuto
Se você precisa coletar 100.000 cartões de produtos de um marketplace ou monitorar preços em tempo real — atrasos simplesmente não ajudam. É necessária uma arquitetura diferente. E é aqui que os proxies se tornam uma necessidade, não uma opção.
É importante entender: rate limiting está vinculado ao endereço IP. Se você tem 100 IPs diferentes — você tem, na verdade, 100 "cotas" independentes. Este é o princípio fundamental para contornar as limitações através de proxies.
Como os proxies resolvem o problema das limitações de solicitações
O mecanismo é simples: cada solicitação ao servidor de destino é feita a partir de um endereço IP diferente. Do ponto de vista do servidor — são usuários diferentes. A cota de cada um deles praticamente não é consumida, portanto, o bloqueio não ocorre.
Vamos considerar a diferença entre trabalhar sem proxies e com um pool de proxies em um exemplo específico. Suponha que o servidor permita 10 solicitações por minuto de um único IP:
| Cenário | Solicitações por minuto | Bloqueio | Tempo para 10.000 solicitações |
|---|---|---|---|
| Um IP, sem proxies | 10 | Sim, após 10 solicitações | ~16 horas |
| 10 proxies, rotação | 100 | Não | ~1.7 horas |
| 100 proxies, rotação | 1000 | Não | ~10 minutos |
Além de escalar a largura de banda, os proxies oferecem ainda várias vantagens ao trabalhar com rate limiting:
- Isolamento de sessões — se um IP for banido, os outros continuam funcionando
- Distribuição geográfica — as solicitações vêm de diferentes regiões, o que reduz a suspeita
- Sticky sessions — possibilidade de "grudar" em um IP para cenários de múltiplas etapas (autenticação + ação)
- Controle de carga — é possível dosar com precisão as solicitações para cada IP, sem exceder o limite
Qual tipo de proxy escolher para sua tarefa
Nem todos os proxies são igualmente eficazes contra rate limiting. A escolha do tipo depende do site-alvo, do volume de solicitações e do orçamento. Vamos discutir três tipos principais:
Proxies residenciais
Estes são endereços IP de usuários residenciais reais. Eles parecem tráfego de internet comum e raramente são bloqueados. Proxies residenciais são a escolha ideal para sites com proteção agressiva: marketplaces (Wildberries, Ozon), redes sociais, recursos protegidos pelo Cloudflare. A principal desvantagem é o preço mais alto em comparação com os de data center.
Proxies móveis
Endereços IP de operadoras móveis (3G/4G/5G). Sua característica é que um IP pode ser usado por milhares de assinantes reais ao mesmo tempo, portanto, bloquear esse endereço é algo que os sites realmente não querem. Proxies móveis mostram os melhores resultados onde os residenciais já começam a ser bloqueados — por exemplo, em scraping de alta frequência do Instagram ou ao trabalhar com APIs de plataformas que analisam o tipo de conexão.
Proxies de data center
IPs rápidos e baratos de data centers. Eles são ideais para scraping de sites sem proteção séria: APIs abertas, agregadores de notícias, bancos de dados públicos. Para tarefas com rate limiting, você precisará de mais deles (pois eles são mais frequentemente incluídos em listas negras), mas com a rotação correta, eles lidam muito bem com grandes volumes de solicitações. Para mais detalhes, veja a página de proxies de data center.
| Tipo de proxy | Anonimato | Velocidade | Preço | Melhor cenário |
|---|---|---|---|---|
| Residenciais | Muito alta | Média | $$ | Marketplaces, redes sociais, Cloudflare |
| Móveis | Máxima | Média | $$$ | API do Instagram, scraping de alta frequência |
| Data centers | Média | Alta | $ | APIs abertas, dados públicos |
Estratégias de rotação de IP: per-request, sticky sessions, round-robin
O simples fato de ter proxies não resolve o problema — é importante gerenciá-los corretamente. Existem várias estratégias de rotação, cada uma adequada para seus cenários.
Rotação per-request (novo IP para cada solicitação)
Cada solicitação HTTP é feita através de um novo endereço IP. Esta é a estratégia mais agressiva para contornar rate limiting — o servidor fisicamente não consegue acumular contagem para um único IP. É adequada para:
- Scraping de cartões de produtos (cada cartão — uma solicitação separada)
- Coleta de dados de motores de busca
- Qualquer solicitação sem estado que não exija sessão
Sticky sessions (IP fixo por sessão)
Um IP é usado durante toda a sessão (geralmente 1–30 minutos). É criticamente importante para cenários onde a autenticação é necessária: entrar na conta, executar uma ação, sair. Se o IP mudar entre as etapas — o servidor pode bloquear a sessão como suspeita.
Round-robin com limite de solicitações por IP
A estratégia mais precisa. Você conhece o limite do servidor (por exemplo, 10 solicitações por minuto) e distribui as solicitações pelo pool de proxies de forma que cada IP nunca exceda esse limite. Requer a implementação de uma fila considerando o tempo da última solicitação para cada IP.
Fórmula para calcular o número necessário de proxies:
N proxies = (Taxa de solicitações alvo/min) ÷ (Limite do servidor/min por IP)
Exemplo: precisa de 500 solicitações/min, limite do servidor — 10/min → precisa de pelo menos 50 proxies.
Adicione 20% de reserva para o caso de bloqueios: total de 60 proxies.
Exemplos de código em Python: requests, aiohttp, Scrapy
Vamos para a prática. Abaixo estão modelos prontos para as três ferramentas Python mais populares.
1. requests + rotação de proxies manualmente
A opção mais simples — uma lista de proxies e escolha aleatória para cada solicitação:
import requests
import random
import time
PROXIES = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
# ... adicione a quantidade necessária
]
def get_random_proxy():
proxy = random.choice(PROXIES)
return {"http": proxy, "https": proxy}
def fetch_with_retry(url, max_retries=3):
for attempt in range(max_retries):
proxy = get_random_proxy()
try:
response = requests.get(
url,
proxies=proxy,
timeout=10,
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
)
if response.status_code == 429:
print(f"Rate limited on {proxy}, switching...")
time.sleep(1)
continue
return response
except requests.RequestException as e:
print(f"Attempt {attempt+1} failed: {e}")
time.sleep(2)
return None
# Uso
urls = ["https://example.com/item/1", "https://example.com/item/2"]
for url in urls:
result = fetch_with_retry(url)
if result:
print(f"OK: {url} — {len(result.text)} bytes")
2. Pool inteligente de proxies considerando rate limit
Uma opção mais avançada — a classe ProxyPool, que rastreia o tempo da última utilização de cada IP e não excede o limite estabelecido:
import requests
import time
from collections import defaultdict
from threading import Lock
class ProxyPool:
def __init__(self, proxies, rate_limit=10, window=60):
"""
proxies: lista de strings no formato 'http://user:pass@host:port'
rate_limit: máximo de solicitações de um IP por janela de tempo
window: janela de tempo em segundos
"""
self.proxies = proxies
self.rate_limit = rate_limit
self.window = window
self.usage = defaultdict(list) # proxy -> [timestamps]
self.lock = Lock()
def get_available_proxy(self):
now = time.time()
with self.lock:
for proxy in self.proxies:
# Limpa timestamps antigos
self.usage[proxy] = [
t for t in self.usage[proxy]
if now - t < self.window
]
if len(self.usage[proxy]) < self.rate_limit:
self.usage[proxy].append(now)
return {"http": proxy, "https": proxy}
return None # Todos os proxies esgotaram o limite
def fetch(self, url, **kwargs):
proxy = self.get_available_proxy()
if proxy is None:
print("Todos os proxies estão limitados, aguardando...")
time.sleep(5)
return self.fetch(url, **kwargs)
try:
response = requests.get(url, proxies=proxy, timeout=10, **kwargs)
return response
except requests.RequestException as e:
print(f"Solicitação falhou: {e}")
return None
# Uso
pool = ProxyPool(
proxies=[
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
],
rate_limit=10, # 10 solicitações por minuto por IP
window=60
)
for i in range(100):
r = pool.fetch(f"https://example.com/page/{i}")
if r:
print(f"Página {i}: {r.status_code}")
3. aiohttp para scraping assíncrono
A abordagem assíncrona permite usar dezenas de proxies em paralelo sem bloquear threads:
import asyncio
import aiohttp
import itertools
PROXIES = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
proxy_cycle = itertools.cycle(PROXIES)
async def fetch(session, url, proxy):
try:
async with session.get(
url,
proxy=proxy,
timeout=aiohttp.ClientTimeout(total=10)
) as response:
if response.status == 429:
await asyncio.sleep(2)
return None
return await response.text()
except Exception as e:
print(f"Erro: {e}")
return None
async def main(urls):
connector = aiohttp.TCPConnector(limit=50)
async with aiohttp.ClientSession(connector=connector) as session:
tasks = [
fetch(session, url, next(proxy_cycle))
for url in urls
]
results = await asyncio.gather(*tasks)
return results
urls = [f"https://example.com/item/{i}" for i in range(200)]
results = asyncio.run(main(urls))
print(f"Coletado: {sum(1 for r in results if r is not None)} páginas")
4. Scrapy com rotação através de middleware
Para Scrapy, existe uma solução pronta — scrapy-rotating-proxies. Mas você pode escrever seu próprio middleware:
# middlewares.py
import random
class RotatingProxyMiddleware:
def __init__(self, proxies):
self.proxies = proxies
@classmethod
def from_crawler(cls, crawler):
return cls(proxies=crawler.settings.getlist("PROXY_LIST"))
def process_request(self, request, spider):
proxy = random.choice(self.proxies)
request.meta["proxy"] = proxy
def process_response(self, request, response, spider):
if response.status == 429:
spider.logger.warning(f"Rate limited, proxy: {request.meta.get('proxy')}")
# Você pode adicionar lógica para excluir o proxy problemático
return response
# settings.py
PROXY_LIST = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.RotatingProxyMiddleware": 350,
}
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 10
Exemplos de código em Node.js: axios, got, Puppeteer
Node.js — uma escolha popular para automação de navegadores e trabalho com APIs. Aqui estão padrões prontos para trabalhar com proxies.
1. axios com rotação de proxies
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const proxies = [
'http://user:[email protected]:8080',
'http://user:[email protected]:8080',
'http://user:[email protected]:8080',
];
let proxyIndex = 0;
function getNextProxy() {
const proxy = proxies[proxyIndex % proxies.length];
proxyIndex++;
return proxy;
}
async function fetchWithProxy(url, retries = 3) {
for (let i = 0; i < retries; i++) {
const proxyUrl = getNextProxy();
const agent = new HttpsProxyAgent(proxyUrl);
try {
const response = await axios.get(url, {
httpsAgent: agent,
httpAgent: agent,
timeout: 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
},
});
return response.data;
} catch (error) {
if (error.response?.status === 429) {
console.log(`Rate limited, switching proxy...`);
await new Promise(r => setTimeout(r, 1000));
continue;
}
console.error(`Attempt ${i + 1} failed:`, error.message);
}
}
return null;
}
// Uso
(async () => {
const urls = Array.from({length: 50}, (_, i) => `https://example.com/item/${i}`);
const results = await Promise.allSettled(
urls.map(url => fetchWithProxy(url))
);
const successful = results.filter(r => r.status === 'fulfilled' && r.value).length;
console.log(`Success: ${successful}/${urls.length}`);
})();
2. Puppeteer com proxies e contornando rate limiting
Para sites com renderização em JavaScript e proteção do Cloudflare, um navegador headless é necessário:
const puppeteer = require('puppeteer');
const proxies = [
'proxy1.example.com:8080',
'proxy2.example.com:8080',
];
async function scrapeWithProxy(url, proxyHost) {
const browser = await puppeteer.launch({
args: [
`--proxy-server=${proxyHost}`,
'--no-sandbox',
'--disable-setuid-sandbox',
],
headless: true,
});
const page = await browser.newPage();
// Autenticação do proxy
await page.authenticate({
username: 'user',
password: 'pass',
});
// Definindo um User-Agent realista
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
);
try {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
// Verificando rate limit
const status = await page.evaluate(() => document.title);
if (status.includes('429') || status.includes('Too Many')) {
console.log('Rate limited, need to switch proxy');
return null;
}
const data = await page.evaluate(() => {
return document.querySelector('.price')?.textContent || null;
});
return data;
} finally {
await browser.close();
}
}
// Rotação por tarefas
(async () => {
const urls = ['https://example.com/product/1', 'https://example.com/product/2'];
for (let i = 0; i < urls.length; i++) {
const proxy = proxies[i % proxies.length];
const result = await scrapeWithProxy(urls[i], proxy);
console.log(`${urls[i]}: ${result}`);
await new Promise(r => setTimeout(r, 500)); // pequena pausa
}
})();
Técnicas avançadas: cabeçalhos, fingerprint, contornando Cloudflare
Mudar de IP — uma condição necessária, mas não sempre suficiente. Sistemas modernos de proteção analisam dezenas de parâmetros da solicitação. Vamos discutir o que mais precisa ser considerado.
Cabeçalhos HTTP: conjunto mínimo obrigatório
Uma solicitação sem cabeçalhos normais parece um bot mesmo com a troca de IP. Sempre adicione:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Cache-Control": "max-age=0",
}
Tratamento do cabeçalho Retry-After
Ao receber um 429, o servidor frequentemente indica quanto tempo deve-se esperar. O tratamento correto desse cabeçalho permite não desperdiçar solicitações:
def handle_rate_limit(response):
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
if retry_after:
wait_time = int(retry_after)
print(f"Rate limited. Waiting {wait_time} seconds...")
time.sleep(wait_time + 1) # +1 segundo de buffer
else:
# Atraso exponencial se o cabeçalho não estiver presente
time.sleep(min(2 ** attempt, 60))
return True
return False
TLS fingerprinting e como contorná-lo
Sistemas avançados (Cloudflare, Akamai, PerimeterX) analisam o fingerprint TLS — uma "impressão digital" única da sua conexão TLS. A biblioteca padrão requests tem um fingerprint facilmente reconhecível. Soluções:
- curl_cffi (Python) — emula o fingerprint do Chrome/Firefox no nível TLS
- tls-client (Go/Python) — ferramenta semelhante com suporte a diferentes perfis de navegador
- Playwright/Puppeteer — navegador real, fingerprint ideal por padrão
# pip install curl-cffi
from curl_cffi import requests as cffi_requests
response = cffi_requests.get(
"https://cloudflare-protected-site.com/api/data",
impersonate="chrome120", # Emulando Chrome 120
proxies={"https": "http://user:[email protected]:8080"}
)
print(response.json())
Gerenciamento de cookies e sessões
Se o site usa cookies para rastrear sessões, mudar de IP sem mudar os cookies é inútil. Ao trocar de proxies, sempre crie uma nova sessão:
import requests
def create_fresh_session(proxy_url):
"""Cria uma nova sessão com cookies limpos para cada proxy"""
session = requests.Session()
session.proxies = {"http": proxy_url, "https": proxy_url}
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
})
# Cookies não são transferidos da sessão anterior
return session
# Para cada novo IP — nova sessão
for proxy in proxies:
session = create_fresh_session(proxy)
response = session.get("https://example.com/protected-page")
# Processa a resposta...
Erros comuns ao trabalhar com proxies e rate limiting
Mesmo com proxies configurados corretamente, os desenvolvedores frequentemente cometem os mesmos erros. Aqui estão os erros mais comuns e como evitá-los.
Checklist: o que verificar antes de iniciar o scraper
- ☐ Cabeçalhos HTTP realistas adicionados (User-Agent, Accept, Accept-Language)
- ☐ Ao trocar de proxy, uma nova sessão é criada (novos cookies)
- ☐ Status 429, 503, 403 tratados com lógica de retry
- ☐ Atraso implementado entre solicitações (pelo menos 100–500 ms)
- ☐ Número de proxies corresponde à taxa de solicitações alvo
- ☐ Proxies testados antes do início (health check)
- ☐ Erros e estatísticas registrados para cada proxy
- ☐ Timeout configurado para solicitações (não mais que 15–30 segundos)
Erro 1: Uso de proxies "mortos"
Sempre verifique os proxies antes de adicioná-los ao pool e periodicamente durante a operação. Um proxy não funcional no loop — são solicitações perdidas e timeouts:
def check_proxy(proxy_url, test_url="https://httpbin.org/ip", timeout=5):
try:
r = requests.get(
test_url,
proxies={"http": proxy_url, "https": proxy_url},
timeout=timeout
)
return r.status_code == 200
except:
return False
# Filtrando proxies funcionais antes do início
working_proxies = [p for p in PROXIES if check_proxy(p)]
print(f"Proxies funcionais: {len(working_proxies)}/{len(PROXIES)}")
Erro 2: Ignorar o tipo de protocolo
Proxies HTTP não podem proxiar tráfego HTTPS diretamente (apenas através de CONNECT). Proxies SOCKS5 funcionam no nível de transporte e suportam qualquer protocolo. Para a maioria dos sites modernos, use SOCKS5 ou proxies HTTPS:
# Proxies SOCKS5 em requests (requer pip install requests[socks])
proxies = {
"http": "socks5://user:[email protected]:1080",
"https": "socks5://user:[email protected]:1080",
}
# Proxies HTTPS
proxies = {
"http": "https://user:[email protected]:8080",
"https": "https://user:[email protected]:8080",
}
Erro 3: Falta de backoff exponencial
Se após um 429 você imediatamente repete a solicitação — você só agrava a situação. A estratégia correta é um atraso exponencial com jitter (desvio aleatório):
import random
def exponential_backoff(attempt, base=1, max_wait=60):
"""
attempt: número da tentativa (começando em 0)
base: atraso base em segundos
max_wait: atraso máximo
"""
wait = min(base * (2 ** attempt), max_wait)
# Jitter ±25% para evitar thundering herd
jitter = wait * 0.25 * random.uniform(-1, 1)
return wait + jitter
# Uso na lógica de retry
for attempt in range(5):
response = requests.get(url, proxies=proxy)
if response.status_code == 429:
wait = exponential_backoff(attempt)
print(f"Rate limited. Waiting {wait:.1f}s (attempt {attempt+1})")
time.sleep(wait)
else:
break
Erro 4: Um thread para todos os proxies
Se você tem 50 proxies, mas uma única thread de execução — você está usando no máximo 1 proxy ao mesmo tempo. Use ThreadPoolExecutor ou uma abordagem assíncrona para usar todo o pool em paralelo:
from concurrent.futures import ThreadPoolExecutor, as_completed
def fetch_url(args):
url, proxy = args
try:
r = requests.get(url, proxies={"https": proxy}, timeout=10)
return url, r.status_code, len(r.text)
except Exception as e:
return url, None, str(e)
# Usando todos os proxies em paralelo
tasks = [(url, proxies[i % len(proxies)]) for i, url in enumerate(urls)]
with ThreadPoolExecutor(max_workers=len(proxies)) as executor:
futures = {executor.submit(fetch_url, task): task for task in tasks}
for future in as_completed(futures):
url, status, size = future.result()
print(f"{url}: {status} ({size})")
Conclusão e recomendações
Rate limiting — um problema solucionável, se abordado de forma sistemática. Principais conclusões deste guia:
- Pool de proxies, não um único proxy — a unidade mínima para trabalho sério. O número de proxies é determinado pela fórmula: taxa alvo ÷ limite do servidor por IP.
- A estratégia de rotação é importante — per-request para solicitações sem estado, sticky sessions para cenários autenticados.
- IP — não é o único parâmetro — cabeçalhos, cookies, fingerprint TLS e padrões de comportamento também são analisados pelos sistemas de proteção.
- Trate 429 corretamente — backoff exponencial, cabeçalho Retry-After, troca de proxies em caso de bloqueio.
- O tipo de proxy depende do objetivo — proxies de data center para APIs abertas, residenciais para marketplaces, móveis para máxima proteção.
Se você está trabalhando com scraping de marketplaces (Wildberries, Ozon), coleta de dados de APIs protegidas ou automação em altas velocidades — recomendamos começar com proxies residenciais: eles oferecem o equilíbrio ideal entre anonimato e velocidade, e seus endereços IP raramente entram em listas negras. Para tarefas que exigem máxima resistência a bloqueios em alta frequência de solicitações, considere proxies móveis — seus IPs são compartilhados por milhares de usuários reais, tornando o bloqueio extremamente indesejável para qualquer site.