Quando um parser ou bot envia centenas de solicitações por minuto de um único IP, o servidor rapidamente ativa o limite de taxa, e a partir daí — captcha ou banimento. A solução é uma fila de tarefas que distribui as solicitações entre trabalhadores e proxies. Neste artigo, vamos explorar como essa arquitetura funciona no Aipo, RQ e BullMQ, e como gerenciar adequadamente o limite de solicitações para cada endereço IP.
Por que usar uma fila de tarefas ao trabalhar com proxies
Se a tarefa é coletar 50.000 produtos da Wildberries ou verificar o status de 10.000 contas do Instagram, a execução sequencial das solicitações levará horas, e a execução paralela sem controle rapidamente resultará em banimento por IP. A fila de tarefas (task queue) resolve ambos os problemas: ela divide um grande volume de trabalho em pequenas tarefas, distribui-as entre vários trabalhadores e permite vincular um proxy separado a cada trabalhador.
A arquitetura geralmente se parece com isso: um broker de mensagens (Redis ou RabbitMQ) armazena a fila de tarefas, os trabalhadores retiram as tarefas da fila e as executam, cada trabalhador usa seu próprio proxy ou rotaciona proxies de um pool. Isso permite manter a carga em cada IP dentro de limites seguros, mesmo que o volume total de solicitações seja enorme.
Essa abordagem é utilizada não apenas por desenvolvedores de parsers, mas também por equipes que automatizam a verificação de contas de anúncios do Facebook Ads, monitoram preços no Ozon ou coletam dados de dezenas de sites ao mesmo tempo. Sem uma fila e uma distribuição adequada por IP, qualquer sistema desse tipo está fadado a bloqueios no primeiro dia de operação.
Problema do limite de solicitações por um IP
A maioria dos sites e APIs possui proteção contra carga excessiva. Isso pode se manifestar de várias maneiras: o servidor retorna HTTP 429 (Too Many Requests), exibe um captcha, bane temporariamente o IP por 10-60 minutos ou o adiciona permanentemente à lista negra. Os limites variam muito entre diferentes plataformas — por exemplo, a API da Wildberries permite um número limitado de solicitações por segundo de um único endereço, enquanto o Instagram reage a atividades suspeitas após 15-20 ações consecutivas.
O principal erro ao construir uma fila é executar todos os trabalhadores através do mesmo IP. Mesmo que as tarefas estejam distribuídas entre 20 trabalhadores, se todos eles usam um único proxy ou não usam nenhum, o limite para esse IP será atingido tão rapidamente quanto na execução sequencial. Portanto, o número de trabalhadores deve corresponder ao número de endereços IP disponíveis no pool de proxies.
Para tarefas com alta frequência de solicitações para um único domínio, é melhor usar proxies de data center — eles oferecem baixa latência e são adequados para scraping paralelo em larga escala, onde a velocidade é mais importante do que se disfarçar como um usuário comum. Se o objetivo é contornar proteções mais rigorosas, como sistemas anti-bot de marketplaces ou redes sociais, são necessários IPs que se pareçam com usuários comuns, ou seja, residenciais ou móveis.
Aipo: arquitetura e rotação de proxies no código
Aipo é a fila de tarefas mais madura e popular no ecossistema Python. Ela funciona sobre um broker (geralmente Redis ou RabbitMQ) e suporta configuração flexível de concorrência, tentativas de repetição e prioridades de tarefas. Para rotação de proxies, é conveniente armazenar um pool de IPs no Redis e atribuir um proxy a cada tarefa ao colocá-la na fila.
from celery import Celery
import requests
import redis
import random
app = Celery('tasks', broker='redis://localhost:6379/0')
redis_client = redis.Redis(host='localhost', port=6379, db=1)
PROXY_POOL = [
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
]
def get_proxy():
# Round-robin através do Redis, para não sobrecarregar um único IP
idx = redis_client.incr("proxy_cursor") % len(PROXY_POOL)
return PROXY_POOL[idx]
@app.task(bind=True, max_retries=3, default_retry_delay=30)
def fetch_url(self, url):
proxy = get_proxy()
try:
response = requests.get(
url,
proxies={"http": proxy, "https": proxy},
timeout=15
)
if response.status_code == 429:
raise self.retry(exc=Exception("Rate limited"))
return response.status_code
except requests.exceptions.RequestException as exc:
raise self.retry(exc=exc)
O ponto chave é a função get_proxy(), que através de um contador Redis fornece o próximo proxy do pool em um ciclo. Isso garante que a carga seja distribuída uniformemente entre todos os IPs, e não se concentre em um único. O parâmetro max_retries e o tratamento do código 429 permitem que a tarefa seja automaticamente reenviada com um proxy diferente, se o IP atual receber uma limitação.
Para controlar a concorrência no nível dos trabalhadores, o Aipo permite limitar o número de tarefas simultâneas através do parâmetro --concurrency ao iniciar o trabalhador, bem como através de filas com diferentes prioridades (por exemplo, uma fila separada para tarefas lentas com limite de taxa rigoroso).
RQ (Redis Queue): uma alternativa simples
RQ é uma biblioteca mais leve do que o Aipo, sem o broker RabbitMQ e com configuração mínima. Ela é adequada se o projeto for pequeno e não exigir roteamento complexo de tarefas, mas apenas uma fila com trabalhadores e a capacidade de vincular proxies a tarefas.
from redis import Redis
from rq import Queue
import requests
import itertools
redis_conn = Redis()
q = Queue(connection=redis_conn)
PROXY_POOL = [
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
]
proxy_cycle = itertools.cycle(PROXY_POOL)
def fetch_with_proxy(url):
proxy = next(proxy_cycle)
response = requests.get(
url,
proxies={"http": proxy, "https": proxy},
timeout=15
)
if response.status_code == 429:
# Recolocação na fila com atraso
q.enqueue_in(60, fetch_with_proxy, url)
return None
return response.status_code
# Colocando tarefas na fila
urls = ["https://example.com/page1", "https://example.com/page2"]
for url in urls:
q.enqueue(fetch_with_proxy, url)
No RQ, a rotação através de itertools.cycle funciona dentro do processo do trabalhador — é mais simples do que um contador Redis, mas não escala bem com muitos trabalhadores paralelos, pois cada trabalhador começa o ciclo do início. Para pequenos projetos com 1-5 trabalhadores, isso é suficiente; para projetos maiores, é melhor voltar a um contador centralizado no Redis, como no exemplo com o Aipo.
O RQ é bem adequado para desenvolvedores que já usam Redis em seus projetos e não querem levantar um broker de mensagens separado. A desvantagem é que há menos mecanismos integrados de priorização e monitoramento em comparação com o Aipo, portanto, para sistemas de produção com centenas de milhares de tarefas por dia, o Aipo é frequentemente escolhido.
BullMQ: fila em Node.js com limite por IP
BullMQ é o padrão para filas de tarefas no ecossistema Node.js, construído sobre Redis e possui um limitador de taxa embutido, o que é especialmente conveniente para controlar o limite de solicitações por IP. Em vez de contar manualmente as solicitações, você pode especificar o limitador diretamente no nível da fila.
const { Queue, Worker } = require('bullmq');
const axios = require('axios');
const connection = { host: 'localhost', port: 6379 };
const proxyPool = [
'http://user:[email protected]:8000',
'http://user:[email protected]:8000',
'http://user:[email protected]:8000',
];
const queue = new Queue('fetchQueue', { connection });
// Limitador: no máximo 5 tarefas por segundo na fila
const worker = new Worker('fetchQueue', async job => {
const proxy = proxyPool[job.id % proxyPool.length];
try {
const response = await axios.get(job.data.url, {
proxy: false,
httpsAgent: new (require('https-proxy-agent'))(proxy),
timeout: 15000,
});
return response.status;
} catch (err) {
if (err.response && err.response.status === 429) {
throw new Error('Rate limited, retrying with another proxy');
}
throw err;
}
}, {
connection,
limiter: { max: 5, duration: 1000 },
concurrency: proxyPool.length,
});
for (let i = 0; i < 100; i++) {
queue.add('fetch', { url: `https://example.com/item/${i}` }, {
attempts: 3,
backoff: { type: 'exponential', delay: 5000 },
});
}
O parâmetro limiter no BullMQ é um mecanismo embutido que limita o número total de tarefas processadas em um intervalo de tempo, o que é conveniente para respeitar o limite de solicitações em todo o sistema. E o parâmetro concurrency, igual ao tamanho do pool de proxies, garante que exatamente o número de trabalhadores disponíveis esteja em operação ao mesmo tempo.
Essa abordagem é frequentemente utilizada por equipes que escrevem seus próprios bots para automatizar ações no TikTok ou Telegram — o BullMQ se integra bem com o restante do stack Node.js e não requer a mudança para Python apenas para uma tarefa.
Comparação entre Aipo, RQ e BullMQ
| Parâmetro | Aipo | RQ | BullMQ |
|---|---|---|---|
| Linguagem | Python | Python | Node.js |
| Broker | Redis / RabbitMQ | Apenas Redis | Apenas Redis |
| Limitador de taxa embutido | Não (implementado manualmente) | Não | Sim, de fábrica |
| Barreira de entrada | Média | Baixa | Média |
| Adequado para | Grandes sistemas de produção | Pequenos projetos, protótipos | Stack Node.js, bots, automação |
Estratégias de distribuição de carga por IP
O round-robin, que é utilizado nos exemplos acima, é a estratégia mais simples, mas nem sempre é a ideal. Aqui estão algumas opções que vale a pena considerar dependendo da tarefa:
- Round-robin — cada solicitação seguinte passa pelo próximo proxy em um ciclo. Adequado quando todos os IPs são equivalentes e os limites são os mesmos.
- Sticky session — um proxy é vinculado a uma conta ou sessão específica durante todo o tempo de uso. Crítico para redes sociais e marketplaces, onde mudar de IP no meio de uma sessão parece suspeito.
- Weighted distribution — parte das solicitações é direcionada a IPs mais caros e confiáveis (por exemplo, móveis), enquanto parte vai para IPs de data center mais baratos, dependendo do domínio que está sendo processado.
- Adaptive throttling — a velocidade de envio de tarefas é automaticamente reduzida se a porcentagem de erros 429 ou captchas aumentar, e é restaurada quando os erros desaparecem.
Para tarefas onde é importante se disfarçar como um usuário real — por exemplo, aquecer e verificar contas no Instagram ou TikTok através de navegadores anti-detectores como Dolphin Anty ou AdsPower — é melhor aplicar sticky session com proxies móveis: eles raramente são suspeitos para sistemas anti-bot e se comportam como usuários normais da internet móvel.
Para coleta massiva de dados de sites, onde a velocidade e o volume são mais importantes do que a confiança do algoritmo, round-robin com proxies residenciais oferece um equilíbrio entre estabilidade e preço: os IPs parecem usuários reais, mas podem ser trocados frequentemente sem risco para uma sessão específica.
Monitoramento de erros 429 e banimentos
Uma fila de tarefas sem monitoramento é uma caixa-preta: é difícil entender por que parte das tarefas falha e quais IPs já estão sob suspeita. O conjunto mínimo de métricas que vale a pena monitorar:
- Porcentagem de respostas com código 429 e 403 para cada proxy separadamente
- Tempo médio de resposta — um aumento na latência frequentemente precede um banimento
- Número de tentativas (retries) por tarefa
- Comprimento da fila de tarefas em tempo real — se ela está crescendo, os trabalhadores não estão conseguindo acompanhar
No Aipo, é conveniente usar o Flower — uma interface web para monitoramento de tarefas e trabalhadores em tempo real. No BullMQ, há o Bull Board com funcionalidade semelhante. Para o RQ, existe o RQ Dashboard. Todas as três ferramentas mostram o status das tarefas, o número de tentativas malsucedidas e permitem entender rapidamente qual proxy do pool deve ser temporariamente excluído da rotação.
Uma boa prática é automaticamente marcar proxies como "em quarentena" por 15-30 minutos após várias falhas consecutivas de 429 desse IP, e retorná-los ao pool apenas após uma solicitação de teste bem-sucedida. Isso evita a situação em que um IP banido continua recebendo tarefas e desperdiçando o tempo dos trabalhadores.
Conclusão
A fila de tarefas resolve o problema de escalabilidade das solicitações, mas por si só não protege contra limites de IP — para isso, é necessária uma rotação de proxies bem planejada dentro do Aipo, RQ ou BullMQ. A escolha da ferramenta depende do stack: projetos em Python geralmente optam pelo Aipo para produção e RQ para tarefas simples, enquanto desenvolvedores Node.js preferem o BullMQ com seu limitador de taxa embutido.
Independentemente da fila escolhida, o resultado depende muito da qualidade do próprio pool de proxies. Se a tarefa é um scraping massivo rápido, considere usar proxies de data center para velocidade, e para trabalhar com redes sociais e marketplaces, onde a lealdade dos sistemas anti-bot é importante, proxies residenciais ou móveis são adequados — eles reduzem a porcentagem de erros 429 e banimentos, o que diretamente diminui o número de tentativas e acelera a execução de toda a fila de tarefas.