Extração de dados

Retry e Backoff: Como Scrapers Transformam Pequenas Falhas em Banimentos

A maioria dos banimentos de scrapers é autoinfligida. Um loop de retry ingênuo responde a uma resposta limitada com uma rajada de tráfego, justamente quando o site pediu menos.

Chris Collins

Chris Collins

23 de agosto de 2026 · 10 min de leitura

Um scraper recebe uma resposta lenta, então tenta de novo. A nova tentativa também falha, então ele tenta novamente, imediatamente, e o mesmo faz cada um dos outros workers que esbarraram no mesmo obstáculo no mesmo momento. Em segundos você enviou uma rajada de tráfego para um site que já estava avisando que queria menos, e o que começou como uma limitação temporária agora é um bloqueio definitivo em todos os endereços envolvidos. O site não escalou. Seu loop de retry escalou.

Essa é a forma mais comum de um scraper bem construído se ferir sozinho, e é totalmente evitável. A lógica de retry é um mecanismo de alívio de carga, não um mecanismo de persistência, e a diferença entre essas duas ideias é a diferença entre um job que degrada com elegância e um que acaba banido. A higiene mais ampla é abordada em avoiding blocks; este texto trata da mecânica do próprio caminho de retry.

Por que retries ingênuos pioram as coisas

Três dinâmicas se combinam, e se combinam juntas.

A primeira é que os retries adicionam carga exatamente quando a carga é o problema. Um 429 ou uma resposta lenta é um pedido para enviar menos, e responder a isso com mais requisições inverte o sinal. A segunda é a sincronização. Workers que falham no mesmo momento e esperam o mesmo intervalo fixo também vão tentar de novo no mesmo momento, então, em vez de se espalharem, chegam como uma rajada coordenada, e cada rodada dessa rajada ressincroniza a próxima. A terceira é que os retries geralmente são contados contra você por endereço, então bombardear um alvo a partir do mesmo IP move esse endereço de limitado para sinalizado, o que é um problema de reputação que sobrevive ao incidente e segue o endereço para o seu próximo job.

Juntando tudo, um loop ingênuo pega uma condição recuperável e produz exatamente o formato de tráfego que os sistemas anti-bot são construídos para detectar. A mesma falha, tratada com moderação, teria se resolvido sozinha.

Classifique antes de tentar novamente

A primeira regra é que nem toda falha merece um retry, e as que merecem, merecem retries diferentes. Separe as respostas em três grupos.

Algumas falhas são transitórias e valem a pena tentar novamente: resets de conexão, timeouts, 502, 503, 504 e 429. Elas representam um sistema que está momentaneamente incapaz, e não indisposto, e o 429 em particular é uma instrução explícita sobre ritmo, não uma recusa. Algumas são definitivas e nunca devem ser repetidas: 404, 400, 401, 403 que persiste em vários endereços, e uma página que foi processada corretamente mas não continha nada do que você queria. Repetir essas falhas queima banda e reputação por um resultado que não pode mudar, e uma falha de parsing é um bug de código que um retry vai reproduzir fielmente para sempre.

O terceiro grupo é o perigoso: respostas que parecem bem-sucedidas e não são. Uma página de desafio, um resultado genérico ou vazio, uma listagem truncada ou um redirecionamento para uma landing page podem chegar com status 200, e um scraper que confia apenas nos códigos de status vai registrá-los como dados com toda tranquilidade. Valide o corpo da resposta antes de contar uma resposta como sucesso, que é a essência de detecting blocked or fake content. Um bloqueio brando é uma falha passível de retry, mas só se você perceber que é uma falha.

Backoff exponencial, e por que o jitter não é opcional

Para o grupo de falhas passíveis de retry, o atraso entre as tentativas deve aumentar, e o formato padrão é exponencial: espere um segundo, depois dois, depois quatro, depois oito. O crescimento importa porque dá a um alvo em dificuldades cada vez mais espaço, em vez de um ritmo constante.

Mas o backoff exponencial sozinho não é suficiente, e essa é a parte que as pessoas pulam. Se cem workers falham juntos e todos esperam exatamente um segundo, eles tentam de novo juntos um segundo depois. O backoff cresceu, mas a rajada sobreviveu, e você simplesmente moveu o mesmo pico ao longo da linha do tempo. A solução é o jitter: randomizar cada atraso em vez de usar o valor calculado diretamente. O full jitter, ou seja, uma espera aleatória escolhida entre zero e o teto atual, transforma uma falha sincronizada em uma distribuição suave de retries. É uma mudança de duas linhas e é a coisa mais eficaz deste artigo.

Mais duas regras acompanham isso. Respeite o Retry-After quando um site o envia, porque esse cabeçalho é o alvo dizendo exatamente quanto tempo você deve esperar, e ignorá-lo em favor do seu próprio cronograma é tanto rude quanto contraproducente. E limite tanto o atraso quanto o número de tentativas, porque uma requisição que já falhou cinco vezes não vai ter sucesso na sexta, e um retry sem limite é apenas uma forma lenta de nunca desistir de algo que já está perdido.

import random, time
import requests

PROXY = "http://customer-USERNAME-country-us:PASSWORD@p.shifter.io:443"
PROXIES = {"http": PROXY, "https": PROXY}

TRANSIENT = {429, 502, 503, 504}
MAX_ATTEMPTS = 5
BASE, CAP = 1.0, 60.0

def fetch(url):
    for attempt in range(MAX_ATTEMPTS):
        try:
            r = requests.get(url, proxies=PROXIES, timeout=20)
        except requests.RequestException:
            pass                                  # transient: fall through to backoff
        else:
            if r.status_code == 200 and is_valid(r.text):
                return r.text                     # validate the body, not just the code
            if r.status_code not in TRANSIENT:
                return None                       # terminal: do not retry
            after = r.headers.get("Retry-After")
            if after:
                time.sleep(min(float(after), CAP)) # the target told you the answer
                continue

        ceiling = min(CAP, BASE * (2 ** attempt))
        time.sleep(random.uniform(0, ceiling))     # full jitter, not a fixed delay
    return None

Rotacionar ou esperar: a decisão que os retries costumam errar

Com proxies residenciais existe um segundo eixo. Uma falha pode ser respondida com tempo, com um endereço diferente, ou com ambos, e escolher errado desperdiça um dos dois.

Espere quando o sinal for sobre a taxa. Um 429 ou um Retry-After é o alvo dizendo que seu ritmo está alto demais, e trocar para um endereço novo para manter o mesmo ritmo é precisamente o comportamento que parece evasão e acaba queimando um pool inteiro em vez de uma única rota. Em vez disso, diminua o ritmo.

Rotacione quando o sinal for sobre o endereço. Uma página de bloqueio, um 403 persistente, ou um desafio que continua aparecendo em uma rota significa que aquele endereço específico não é mais confiável, e esperar não vai restaurá-lo. Aposente a rota e continue com uma nova, que é o padrão de failover, e observe que a reputação do substituto é o que determina se o retry realmente ajuda. O único caso em que rotacionar está errado é o trabalho em meio a uma sessão: se um fluxo depende de uma identidade mantida, trocar de endereço o quebra, então uma falha dentro de uma sticky session significa reiniciar a sequência em uma nova sessão em vez de trocar os endereços por baixo da sessão existente.

Os timeouts ficam entre os dois e merecem um diagnóstico próprio em vez de um reflexo, já que os motivos pelos quais as requisições dão timeout incluem lentidão do alvo, uma rota com problemas, e sua própria concorrência sendo alta demais.

Orçamentos e circuit breakers

As regras de retry por requisição não são suficientes sozinhas, porque não têm visão do sistema como um todo. Dois mecanismos dão essa visão.

Um orçamento de retry limita os retries como proporção do tráfego total, por exemplo permitindo que os retries sejam no máximo dez por cento das requisições a um determinado alvo. Em condições normais, o orçamento nunca é tocado. Quando algo quebra de forma ampla, o orçamento se esgota imediatamente e os retries extras simplesmente não acontecem, que é a propriedade que você quer: retries ajudam com falhas isoladas e são ativamente prejudiciais durante uma queda geral, e um orçamento é o que faz essa distinção automaticamente.

Um circuit breaker vai além. Acompanhe a taxa de falha por alvo, e quando ela ultrapassa um limite, pare de enviar para esse alvo completamente por um período de resfriamento em vez de continuar sondando-o com um gotejamento de requisições fadadas ao fracasso. Depois do resfriamento, deixe passar um pequeno número de requisições, e se tiverem sucesso, retome. Isso protege o alvo do seu acúmulo de tentativas e protege seus endereços de acumularem falhas contra um site que não está respondendo a ninguém no momento. Ambos os mecanismos são por alvo, não globais, porque um site quebrado nunca deveria parar um job que está coletando de outros cinquenta.

Retries custam dinheiro e se escondem nos seus dados

Duas consequências que vale a pena declarar claramente.

Cada retry é uma requisição pela qual você paga. Em proxies residenciais com preço por banda, uma tempestade de retries é um item de custo, e um job que silenciosamente tenta de novo cinco vezes contra um site que está fora do ar a tarde toda pode movimentar uma quantidade surpreendente de dados sem retorno nenhum, o que é um dos contribuintes menos óbvios para o quadro de custos em cutting proxy bandwidth costs.

E a taxa de retry é um indicador antecedente que merece estar no seu dashboard. Uma taxa de retry crescente em um alvo é o primeiro aviso de que suas defesas mudaram ou que seu ritmo se desviou, e isso aparece bem antes de a taxa de sucesso desmoronar, então monitorar o pipeline deve acompanhar tentativas e resultados, não apenas os resultados finais. Um scraper que silenciosamente tenta de novo até alcançar uma taxa de sucesso de aparência normal está escondendo o problema, não resolvendo-o.

Por fim, quando uma requisição esgota suas tentativas, não a descarte. Envie-a para uma dead-letter queue e tente novamente bem mais tarde, na próxima execução ou depois de um longo resfriamento, em vez de dentro da rajada atual. A maioria dos itens que falham durante um incidente tem sucesso sozinha uma hora depois, e uma fila transforma uma falha definitiva em uma falha adiada.

Conclusão

A lógica de retry existe para absorver falhas transitórias, não para insistir. Classifique a falha antes de agir, tente novamente apenas o que é genuinamente transitório, e valide os corpos das respostas para que um bloqueio brando seja tratado como a falha que é. Faça backoff exponencial e sempre com jitter, respeite o Retry-After, e limite tanto o atraso quanto as tentativas. Distinga sinais de taxa, que pedem espera, de sinais de endereço, que pedem rotação, e nunca responda a uma limitação alternando endereços para sustentar o mesmo ritmo. Adicione um orçamento de retry e um circuit breaker por alvo para que uma queda ampla não se transforme em uma enxurrada autoinfligida. Faça isso e a maioria dos banimentos que as equipes atribuem à escalada de anti-bot simplesmente para de acontecer, porque nunca foi escalada, para começar.

A outra metade é ter para onde fazer failover, que é o que os proxies residenciais fornecem: um grande pool de IPs reais, de qualidade doméstica, para que uma rota aposentada seja substituída por uma limpa, em vez de pelo mesmo endereço tentando de novo. O preço por GB também é o motivo pelo qual retries disciplinados se pagam sozinhos, já que você só paga pelas requisições que de fato faz, e uma tempestade que você nunca envia é banda que você nunca compra.

Pronto para começar?

Experimente os proxies residenciais da Shifter, mais de 205M IPs, mais de 195 países, a partir de $0,75/GB.

Começar