← Voltar ao blog

Parser distribuído em vários servidores: divisão do pool de proxies sem conflitos e banimentos

Analisamos a arquitetura de um parser distribuído em vários servidores - como dividir corretamente o pool de proxies, evitar a duplicação de solicitações de um único IP e minimizar bans.

📅21 de outubro de 2026

Quando o parser se muda de um servidor para um cluster de vários workers, surge um novo problema — várias máquinas começam a usar o mesmo pool de proxies ao mesmo tempo. Sem coordenação, isso leva à duplicação de IPs nas solicitações, superação dos limites por um único endereço e bans em massa. Neste artigo, vamos discutir como shardear corretamente os proxies entre servidores, quais esquemas de distribuição funcionam melhor e como implementar isso na prática com código em Python e Redis.

Por que os proxies entram em conflito em um parser distribuído

Vamos imaginar uma situação típica: você tem um pool de 500 proxies e três servidores, cada um executando 50 workers de parsing paralelos do Wildberries, Ozon ou Avito. Se cada servidor pega proxies da lista comum de forma independente, dois workers em máquinas diferentes podem enviar simultaneamente uma solicitação do mesmo IP para o mesmo domínio. Para o sistema anti-bot, isso parece uma frequência anormalmente alta de solicitações de um único endereço — e o IP é banido em questão de minutos.

O segundo problema é a distribuição desigual da carga. Se os proxies são simplesmente divididos "como der", um servidor pode pegar os IPs mais rápidos e novos, enquanto o outro fica com os que já têm uma reputação comprometida. Como resultado, a velocidade de parsing nos servidores pode variar drasticamente, assim como a taxa de erros.

O terceiro problema é a perda de estado. Se um proxy é banido em um servidor, os outros servidores não saberão e continuarão a usá-lo, até que também recebam um ban. Sem um registro centralizado do status dos proxies, cada servidor "redescobre a América" novamente, desperdiçando solicitações.

A solução para os três problemas é implementar um mecanismo de coordenação único: seja através de um armazenamento centralizado de estado (Redis, PostgreSQL, Consul), ou através de uma divisão determinística do pool previamente, sem sobreposições de intervalos entre servidores.

Arquitetura do parser distribuído

Antes de dividir os proxies, é necessário definir a arquitetura geral do cluster. Existem duas abordagens básicas.

Esquema estático (shared-nothing). O pool de proxies é dividido entre os servidores previamente, uma vez no início. Cada servidor recebe seu segmento não sobreposto e trabalha com ele de forma autônoma, sem recorrer a outros servidores. A vantagem — máxima simplicidade e resiliência: a falha de um servidor não afeta os outros. A desvantagem — se metade dos proxies for banida em um servidor, ele não pode "pegar emprestado" IPs novos do vizinho.

Esquema dinâmico (shared-state). Todos os servidores acessam um armazenamento comum (geralmente Redis), que armazena o status de cada proxy e o fornece sob demanda com bloqueio. A vantagem — utilização uniforme do pool e resposta instantânea a bans. A desvantagem — o armazenamento se torna um único ponto de falha, e é necessária uma lógica separada para o caso de sua indisponibilidade.

Na prática, para parsing de marketplaces e redes sociais, geralmente é escolhido um híbrido: um broker no Redis fornece proxies com bloqueio por um curto período (TTL), e na indisponibilidade do Redis, os servidores temporariamente mudam para um pool de fallback local. Essa abordagem combina a confiabilidade do esquema estático com a flexibilidade do dinâmico.

Métodos de divisão do pool de proxies entre servidores

Vamos considerar algoritmos específicos que podem ser aplicados para dividir o pool.

Divisão simples por módulo (round-robin sharding). Os proxies são numerados de 0 a N-1, o servidor com o número i pega todos os proxies onde index % número_de_servidores == i. Simples, mas não lida bem com a adição ou remoção de servidores — ao mudar o número de máquinas, quase todo o pool é redistribuído novamente.

Hashing consistente. Cada servidor e cada proxy recebe uma posição no anel de hashes (geralmente pelo hash do endereço IP ou ID do servidor). O proxy é vinculado ao servidor mais próximo no anel. Ao adicionar ou remover um servidor, apenas uma pequena parte dos proxies é redistribuída, e não todo o pool — essa é a principal vantagem para clusters que escalam dinamicamente (por exemplo, no Kubernetes com autoescalonamento).

Broker centralizado com bloqueios. Em vez de vincular rigidamente um proxy a um servidor, todos os servidores solicitam um proxy "livre" de um serviço comum (Redis, RabbitMQ), que o fornece com um bloqueio temporário. Após o uso, o proxy é devolvido ao pool. Esta é a opção mais flexível, especialmente se a carga nos servidores for desigual.

Divisão por domínios ou tarefas. Se o parser trabalha com várias fontes ao mesmo tempo (por exemplo, Wildberries, Ozon e Avito em paralelo), faz sentido dividir não apenas os proxies entre os servidores, mas também os proxies entre as fontes — para que o mesmo IP não apareça em várias plataformas ao mesmo tempo. Isso reduz o risco de que o IP entre em uma lista negra comum de sistemas anti-fraude, caso essas plataformas compartilhem dados.

Tabela: comparação de métodos de sharding de proxies

Método Complexidade Resiliência ao escalonamento Quando aplicar
Divisão por módulo Baixa Ruim Número fixo de servidores
Hashing consistente Média Boa Autoescalonamento do cluster
Broker centralizado Média-alta Excelente Carga desigual, tráfego alto
Divisão por domínios Baixa Média Parsing de várias plataformas simultaneamente

Implementação de um broker de proxies no Redis

A opção mais prática para a maioria dos parsers distribuídos é um broker centralizado no Redis. O Redis é adequado porque é rápido, suporta operações atômicas e TTL "pronto para uso". A ideia é simples: todos os proxies disponíveis estão em um Redis Set, e ao fornecer um proxy a um worker específico, ele é bloqueado por um determinado tempo através de uma chave com TTL.

Abaixo está um exemplo funcional de um broker em Python usando a biblioteca redis-py. Ele implementa a entrega de um proxy livre com bloqueio, a liberação após o uso e a marcação do proxy como banido.

import redis
import time
import random

r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)

PROXY_POOL_KEY = "proxy:pool"       # conjunto de todos os proxies ativos
LOCK_PREFIX = "proxy:lock:"         # prefixo de bloqueio de um proxy específico
BANNED_PREFIX = "proxy:banned:"     # prefixo de marcação de banimento
LOCK_TTL = 30                       # segundos para bloquear o proxy
BAN_TTL = 3600                      # segundos para considerar o proxy banido

def load_pool(proxy_list):
    """Carrega a lista de proxies no Redis uma vez ao iniciar o cluster."""
    if proxy_list:
        r.sadd(PROXY_POOL_KEY, *proxy_list)

def get_free_proxy(server_id, max_attempts=20):
    """Cada servidor chama esta função para obter um proxy livre."""
    candidates = list(r.smembers(PROXY_POOL_KEY))
    random.shuffle(candidates)

    for proxy in candidates[:max_attempts]:
        if r.exists(BANNED_PREFIX + proxy):
            continue
        lock_key = LOCK_PREFIX + proxy
        # definição atômica de bloqueio, se o proxy estiver livre
        acquired = r.set(lock_key, server_id, nx=True, ex=LOCK_TTL)
        if acquired:
            return proxy
    return None  # nenhum proxy livre encontrado, fallback necessário

def release_proxy(proxy):
    """Libera o proxy após uma solicitação bem-sucedida."""
    r.delete(LOCK_PREFIX + proxy)

def mark_banned(proxy):
    """Marca o proxy como banido por BAN_TTL segundos, remove o bloqueio."""
    r.set(BANNED_PREFIX + proxy, 1, ex=BAN_TTL)
    r.delete(LOCK_PREFIX + proxy)
    r.srem(PROXY_POOL_KEY, proxy)  # remove temporariamente do pool

def return_to_pool_if_recovered():
    """Tarefa periódica: retorna proxies cujo banimento expirou."""
    for proxy in r.scan_iter(match=BANNED_PREFIX + "*"):
        clean_proxy = proxy.replace(BANNED_PREFIX, "")
        if not r.exists(BANNED_PREFIX + clean_proxy):
            r.sadd(PROXY_POOL_KEY, clean_proxy)

O ponto chave aqui é o uso de SET ... NX EX no Redis. Este comando é atômico: se dois servidores tentarem bloquear o mesmo proxy ao mesmo tempo, apenas um deles terá sucesso, o segundo receberá uma falha e irá para o próximo candidato da lista. Isso é o que elimina conflitos entre servidores ao acessar simultaneamente o pool comum.

Exemplo de uso do broker em um worker do parser:

import requests

def fetch_with_proxy(url, server_id):
    proxy = get_free_proxy(server_id)
    if not proxy:
        raise RuntimeError("Não há proxies livres no pool")

    proxies = {"http": f"http://{proxy}", "https": f"http://{proxy}"}
    try:
        response = requests.get(url, proxies=proxies, timeout=10)
        if response.status_code in (403, 429):
            mark_banned(proxy)
        else:
            release_proxy(proxy)
        return response
    except requests.exceptions.RequestException:
        mark_banned(proxy)
        raise

Esse esquema é facilmente escalável — você pode adicionar 10 ou 50 servidores, eles irão acessar a mesma instância do Redis, e não haverá conflitos de IP, porque o bloqueio é atômico no nível do armazenamento, e não no nível de um servidor específico.

Sessões sticky e vinculação de proxies a tarefas

Para algumas tarefas, simplesmente fornecer "qualquer proxy livre" não é suficiente. Se o parser emula o comportamento do usuário — por exemplo, visualizando sequencialmente cartões de produtos, adicionando ao carrinho, navegando pelas páginas do catálogo — é importante que todas as solicitações de uma sessão venham do mesmo IP. Caso contrário, o site verá uma mudança abrupta de geolocalização ou dispositivo no meio da sessão e a bloqueará como suspeita.

Para isso, introduz-se o conceito de sessão sticky: o proxy é vinculado não a uma única solicitação, mas a uma tarefa específica ou sessão do usuário durante sua vida útil. No Redis, isso é implementado através de uma chave separada do tipo session:{task_id} -> proxy com TTL igual à duração esperada da sessão.

SESSION_PREFIX = "proxy:session:"
SESSION_TTL = 600  # 10 minutos para a sessão

def get_sticky_proxy(task_id, server_id):
    existing = r.get(SESSION_PREFIX + task_id)
    if existing:
        return existing
    proxy = get_free_proxy(server_id)
    if proxy:
        r.set(SESSION_PREFIX + task_id, proxy, ex=SESSION_TTL)
    return proxy

Para tarefas com sessões longas — por exemplo, navegando pelo catálogo do Wildberries emulando o comportamento de um comprador real — é crítico usar proxies que se comportem o mais próximo possível de um usuário comum. Aqui, os proxies residenciais são uma boa opção, pois estão vinculados a IPs domésticos reais e raramente levantam suspeitas nos sistemas anti-bot dos marketplaces em comparação com endereços de data centers.

Tratamento de bans e rotação no cluster

Em um sistema distribuído, é importante que a informação sobre o banimento de um proxy se torne instantaneamente acessível a todos os servidores, e não apenas àquele que recebeu o ban. No exemplo acima, isso é tratado pela função mark_banned — assim que um servidor registra um 403 ou 429 do site alvo, o proxy é removido do pool comum no Redis, e os outros servidores automaticamente param de recebê-lo.

Para determinar um ban, é importante analisar não apenas o código de resposta HTTP, mas também o conteúdo da página — alguns sites retornam 200 OK, mas com um captcha ou uma página de bloqueio em vez do conteúdo real. A lógica de detecção de ban geralmente se parece com isto:

  • Códigos HTTP 403, 429, 503 — sinais claros de bloqueio ou limite de taxa.
  • Redirecionamento para uma página de captcha ou autenticação — verificação da URL após a solicitação.
  • Redução abrupta no tamanho da resposta ou ausência de seletores CSS esperados — sinal de bloqueio.
  • Timeout de conexão — frequentemente significa que o proxy parou de responder completamente.

Após a detecção de um ban, faz sentido não apenas remover o proxy do pool para sempre, mas colocá-lo em "quarentena" com TTL (como no exemplo — BAN_TTL), após o qual o proxy retorna automaticamente ao pool. Muitos bloqueios em marketplaces e redes sociais são temporários e são removidos após 30-60 minutos.

Se os bans ocorrem com muita frequência, mesmo com uma distribuição uniforme da carga, isso geralmente é um sinal de que o próprio pool de proxies tem baixa qualidade ou tamanho insuficiente para o volume de solicitações necessário. Nesses casos, expandir o pool com proxies de data centers para tarefas menos sensíveis (por exemplo, verificações de disponibilidade de páginas) e usar IPs residenciais mais caros apenas para sessões críticas ajuda a reduzir a taxa geral de bans sem aumentar os custos de todo o tráfego.

Monitoramento da saúde do pool de proxies

Em um cluster de vários servidores, é fácil perder a visão geral: quantos proxies estão ativos, quantos estão banidos, qual é a velocidade média de resposta de cada um. Sem monitoramento, é difícil entender por que o parsing desacelerou — se um servidor caiu ou se acabaram os proxies ativos.

O conjunto mínimo de métricas que vale a pena coletar no Redis ou em um banco de dados separado:

  • Número de proxies ativos no pool no momento atual.
  • Número de proxies banidos e tempo médio até sua recuperação.
  • Tempo médio de resposta de cada proxy nas últimas N solicitações.
  • Taxa de solicitações bem-sucedidas (success rate) por servidores e pelo pool como um todo.
  • Distribuição da carga — quantas solicitações cada servidor processou em um período.

Para visualização, é conveniente agregar essas métricas no Prometheus através de um simples exportador, que lê os dados do Redis a cada minuto e os fornece em um formato compreensível para o Grafana. Isso permite ver rapidamente a queda na taxa de sucesso em um determinado geolocalização ou tipo de proxy e adicionar novos IPs ao pool a tempo, antes que o parser pare completamente.

É importante monitorar picos de bans ao iniciar novos servidores no cluster — se ao escalar de 3 para 6 servidores a taxa de bans aumentou drasticamente, provavelmente o problema está no sharding: os novos servidores começaram a se sobrepor nos intervalos de proxies com os antigos. Este é o primeiro sinal de que é necessário mudar de divisão por módulo para hashing consistente.

Checklist para iniciar o parser distribuído

Antes de implantar o parser em vários servidores, verifique os seguintes pontos:

  • Mecanismo de coordenação do pool escolhido: divisão estática, hashing consistente ou broker centralizado no Redis.
  • Bloqueio atômico de proxies implementado, excluindo o uso de um IP por dois servidores ao mesmo tempo.
  • TTL configurado para bloqueio e para banimento, para que os proxies não "fiquem presos" em estado bloqueado para sempre em caso de falha do worker.
  • Para tarefas que requerem emulação da sessão do usuário, vinculação de proxies sticky durante todo o período da tarefa.
  • Detecção de bans configurada não apenas pelo código HTTP, mas também pelo conteúdo da resposta.
  • Monitoramento da taxa de sucesso, proxies ativos e banidos em todo o cluster habilitado.
  • Pool de proxies dividido por tipos de tarefas: IPs de data centers — para solicitações menos sensíveis, proxies residenciais ou proxies móveis — para sessões onde a semelhança com um usuário real é crítica.
  • Carga no armazenamento de estado (Redis) verificada — com um grande número de servidores e alta frequência de solicitações, pode ser necessário um cluster Redis em vez de uma única instância.

Conclusão

A principal razão para conflitos ao mover o parser para vários servidores é a falta de uma única fonte de verdade sobre o estado do pool de proxies. Assim que um broker centralizado com bloqueios atômicos (no Redis ou em um armazenamento semelhante) é introduzido, o problema de duplicação de IPs e desincronização de bans desaparece por si só. Depois, resta escolher a estratégia de sharding adequada ao tamanho do seu cluster: para um número fixo de servidores, uma divisão simples é suficiente, enquanto para uma infraestrutura que cresce dinamicamente, é melhor planejar o hashing consistente desde o início.

Se o seu parser distribuído trabalha com marketplaces ou redes sociais, onde a verossimilhança do tráfego em nível de sessão é importante, vale a pena dividir o pool por tipo de tarefa: para escaneamento em massa de catálogos, proxies de data centers mais baratos são adequados, enquanto para sessões que emulam o comportamento do usuário, proxies residenciais com IPs domésticos reais, que reduzem significativamente a taxa de bans em sistemas sensíveis a fraudes, são recomendados.