Todo scraper tenta novamente requisições que falham, e a maioria escolhe o número de tentativas da mesma forma: três, porque parece razoável. Mas as tentativas não são gratuitas. Em proxies residenciais cobrados por gigabyte, cada nova tentativa baixa a página novamente; em qualquer sistema, isso custa tempo e carga sobre o alvo. E cada tentativa costuma ter menos chance de sucesso do que a anterior.
Então existe um ponto em que mais uma tentativa custa mais do que vale. Este estudo mostra como encontrar esse ponto a partir dos seus próprios logs, com um exemplo prático e o código para rodar sobre seus dados.
Por que tentativas posteriores têm menos sucesso
Se as falhas fossem puro azar, cada tentativa teria a mesma chance de sucesso que a primeira. Na prática, não é assim. Uma requisição que falha uma vez tem mais chance de estar falhando por um motivo persistente: uma página que não existe, um bloqueio naquele endereço, um site fora do ar, um parser que nunca encontra correspondência. Os trabalhos que chegam à quarta tentativa são, em sua maioria, os casos difíceis.
Isso significa que a taxa de sucesso por tentativa cai, e o custo de cada sucesso extra sobe junto.
Meça isso a partir dos seus logs
O único dado que você precisa é, para cada trabalho, o número da tentativa que teve sucesso, ou uma marcação de que todas as tentativas falharam. A maioria dos scrapers já registra isso. A partir daí, a taxa de sucesso da tentativa k é a fração dos trabalhos que chegaram à tentativa k e tiveram sucesso nela.
from collections import Counter
def attempt_success_rates(outcomes, max_attempts):
"""Chance that attempt k succeeds, given the job got that far.
outcomes: per job, the attempt number that succeeded, or None if every
attempt failed (with max_attempts tries).
"""
succeeded_at = Counter(o for o in outcomes if o is not None)
reached = len(outcomes)
rates = []
for k in range(1, max_attempts + 1):
rates.append(succeeded_at[k] / reached if reached else 0.0)
reached -= succeeded_at[k]
return rates
def cost_per_success(rates, cap, attempt_cost):
"""Expected cost of one successful job if we stop after `cap` attempts."""
p_reach, spent, success = 1.0, 0.0, 0.0
for rate in rates[:cap]:
spent += p_reach * attempt_cost
success += p_reach * rate
p_reach *= 1 - rate
return spent / success if success else float("inf")
def best_cap(rates, attempt_cost, value_of_success):
"""The last attempt whose expected value still exceeds its cost."""
cap = 0
for k, rate in enumerate(rates, start=1):
if rate * value_of_success > attempt_cost:
cap = k
return cap
Três funções:
attempt_success_ratestransforma seu log em uma taxa de sucesso por tentativa.cost_per_successfornece o custo médio de um trabalho bem-sucedido se você parar após um certo número de tentativas.best_capencontra a última tentativa cujo valor esperado ainda é maior que seu custo.
Um exemplo prático
Aqui está um log ilustrativo de 10.000 trabalhos, cada um com até seis tentativas permitidas:
from retry_econ import attempt_success_rates, best_cap, cost_per_success
# Illustrative outcomes for 10,000 jobs, each allowed up to six attempts:
# the attempt that succeeded, or None if all six failed.
outcomes = [1] * 8200 + [2] * 900 + [3] * 260 + [4] * 90 + [5] * 40 + [6] * 20 + [None] * 490
rates = attempt_success_rates(outcomes, max_attempts=6)
print("success rate by attempt:", [round(r, 3) for r in rates])
for cap in range(1, 7):
print(f"cap {cap}: cost per success {cost_per_success(rates, cap, attempt_cost=1.0):.3f}")
print("best cap if a success is worth 5 attempts:", best_cap(rates, attempt_cost=1.0, value_of_success=5.0))
print("best cap if a success is worth 20 attempts:", best_cap(rates, attempt_cost=1.0, value_of_success=20.0))
success rate by attempt: [0.82, 0.5, 0.289, 0.141, 0.073, 0.039]
cap 1: cost per success 1.220
cap 2: cost per success 1.297
cap 3: cost per success 1.357
cap 4: cost per success 1.412
cap 5: cost per success 1.464
cap 6: cost per success 1.514
best cap if a success is worth 5 attempts: 3
best cap if a success is worth 20 attempts: 5
Três coisas chamam a atenção.
A taxa de sucesso cai rápido. A primeira tentativa tem sucesso em 82% dos casos. Dos trabalhos que chegam à segunda tentativa, metade tem sucesso. Na quinta tentativa, menos de um em dez consegue.
Cada sucesso extra custa mais do que o anterior. Dividindo o custo de uma tentativa pela sua taxa de sucesso, obtemos o custo de um sucesso a partir daquela tentativa: cerca de 1,2 tentativa na primeira, 2 na segunda, 3,5 na terceira, 7 na quarta e mais de 25 na sexta. O custo médio por sucesso sobe lentamente à medida que se permite mais tentativas, porque cada tentativa extra ocorre sobre menos trabalhos, mas o custo marginal sobe de forma acentuada.
O limite certo depende do valor de um sucesso. Se um registro bem-sucedido vale cinco tentativas de custo, pare na terceira. Se vale vinte, como pode ser o caso de uma página que você precisa obter a qualquer custo, vá até a quinta. Não existe um número único correto; existe um número correto para cada valor.
Atribuindo um valor a um sucesso
O valor de um sucesso é o único dado que você precisa decidir. Algumas formas de definí-lo:
- Registros substituíveis, como uma observação de preço entre muitas coletadas diariamente, valem pouco cada uma. Perder uma não é problema; um limite baixo é adequado.
- Registros obrigatórios, como uma página que completa um conjunto de dados que um cliente está esperando, valem muito mais. Um limite mais alto faz sentido.
- Custos além da banda também contam: tempo, carga sobre o alvo e a chance de que insistir em uma página com falha provoque um bloqueio que afete tudo o mais.
Em uma API de web scraping que cobra apenas por respostas bem-sucedidas, o lado financeiro de uma nova tentativa muda para o provedor, mas o tempo e a carga sobre o alvo continuam valendo. A Web Scraping API da Shifter, por exemplo, não cobra por requisições com falha.
Tornando as tentativas mais baratas
O limite é apenas metade da questão. A outra metade é fazer com que cada nova tentativa tenha mais chance de sucesso:
- Espere antes de tentar novamente, e espere mais a cada vez. Muitas falhas são limites de taxa temporários que se resolvem sozinhos.
- Mude algo quando a falha sugerir isso. Um bloqueio em um endereço pode ter sucesso a partir de outro; um tempo limite pode precisar de um tempo limite maior, não da mesma requisição repetida.
- Não tente novamente o que não pode dar certo. Um erro 404 ou de autenticação falhará sempre.
Nosso guia sobre retry e backoff para web scrapers cobre essas técnicas em detalhe.
Aplique isso aos seus próprios dados
- Exporte, de uma semana de trabalhos, a tentativa que teve sucesso ou um marcador de falha.
- Execute
attempt_success_ratescom seu máximo atual. - Decida quanto vale um sucesso em número de tentativas.
- Defina o limite conforme o retorno de
best_cap, por tipo de trabalho, se eles diferirem. - Reavalie mensalmente. As taxas mudam conforme os sites e sua configuração mudam.
Perguntas frequentes
Quantas vezes um scraper deve tentar novamente uma requisição com falha?
Depende de quão rápido sua taxa de sucesso por tentativa cai e de quanto vale um registro bem-sucedido. Meça a taxa de sucesso de cada tentativa a partir dos seus logs, depois pare na última tentativa cujo valor esperado ainda seja maior que seu custo. Para muitos trabalhos, isso fica entre duas e quatro tentativas.
Por que as tentativas posteriores têm menos sucesso do que as primeiras?
Porque as falhas não são aleatórias. Trabalhos que falham uma vez têm mais chance de ter um problema persistente, como uma página ausente ou um bloqueio, então cada tentativa posterior é extraída de casos mais difíceis.
As tentativas repetidas custam dinheiro com proxies residenciais?
Sim. O tráfego de proxy residencial é cobrado por volume, então cada nova tentativa baixa a página novamente. Tentativas com falha que retornam páginas de erro grandes também consomem banda.
Devo tentar novamente erros 404?
Não. Uma página que não existe não vai aparecer na próxima tentativa. Tente novamente apenas falhas que podem ter sucesso depois, como tempos limite, limites de taxa e erros temporários de servidor.
Conclusão
Vale a pena tentar novamente até que a chance de a próxima tentativa ter sucesso, multiplicada pelo valor de um sucesso, caia abaixo do custo da tentativa. Seus logs já contêm a taxa de sucesso de cada tentativa; meça-a, atribua um valor a um sucesso e defina o limite a partir dos números, em vez de por hábito.