Chaque scraper relance les requêtes échouées, et la plupart choisissent le nombre de tentatives de la même façon : trois, parce que cela semble raisonnable. Mais les nouvelles tentatives ne sont pas gratuites. Sur des proxies résidentiels facturés au gigaoctet, chaque nouvelle tentative télécharge à nouveau la page ; sur n’importe quel système, elle coûte du temps et de la charge sur la cible. Et chaque nouvelle tentative a généralement moins de chances de réussir que la précédente.
Il existe donc un point où une tentative supplémentaire coûte plus qu’elle ne rapporte. Cette étude montre comment le déterminer à partir de vos propres journaux, avec un exemple détaillé et le code à exécuter sur vos données.
Pourquoi les tentatives tardives réussissent moins souvent
Si les échecs étaient purement dus à la malchance, chaque nouvelle tentative aurait la même chance de succès que la première. En pratique, ce n’est pas le cas. Une requête qui échoue une fois a plus de chances d’échouer pour une raison persistante : une page qui n’existe pas, un blocage sur cette adresse, un site hors service, un analyseur qui ne corree jamais. Les tâches qui atteignent la quatrième tentative sont pour la plupart des cas difficiles.
Cela signifie que le taux de succès par tentative diminue, et que le coût de chaque succès supplémentaire augmente en conséquence.
Mesurez-le à partir de vos journaux
La seule donnée dont vous avez besoin est, pour chaque tâche, le numéro de la tentative qui a réussi, ou une note indiquant que toutes les tentatives ont échoué. La plupart des scrapers consignent déjà cette information. À partir de là, le taux de succès de la tentative k est la part des tâches qui ont atteint la tentative k et ont réussi à ce moment-là.
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
Trois fonctions :
attempt_success_ratestransforme votre journal en un taux de succès par tentative.cost_per_successdonne le coût moyen d’une tâche réussie si l’on s’arrête après un nombre donné de tentatives.best_captrouve la dernière tentative dont la valeur attendue est encore supérieure à son coût.
Un exemple détaillé
Voici un journal illustratif de 10 000 tâches, chacune autorisée à six tentatives :
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
Trois éléments ressortent.
Le taux de succès chute rapidement. La première tentative réussit 82% du temps. Parmi les tâches qui atteignent la deuxième tentative, la moitié réussissent. À la cinquième tentative, moins d’une sur dix réussit.
Chaque succès supplémentaire coûte plus cher que le précédent. En divisant le coût d’une tentative par son taux de succès, on obtient le coût d’un succès à partir de cette tentative : environ 1,2 tentative pour la première, 2 pour la deuxième, 3,5 pour la troisième, 7 pour la quatrième et plus de 25 pour la sixième. Le coût moyen par succès augmente lentement à mesure que l’on autorise plus de tentatives, car chaque tentative supplémentaire ne s’applique qu’à moins de tâches, mais le coût marginal grimpe fortement.
Le bon plafond dépend de la valeur d’un succès. Si un enregistrement réussi vaut le coût de cinq tentatives, arrêtez-vous après trois. S’il en vaut vingt, comme cela pourrait être le cas pour une page que vous devez absolument obtenir, allez jusqu’à cinq. Il n’y a pas un seul bon nombre ; il y a un bon nombre pour chaque valeur.
Attribuer une valeur à un succès
La valeur d’un succès est la seule donnée que vous devez fixer vous-même. Quelques façons de la définir :
- Les enregistrements remplaçables, comme une observation de prix parmi beaucoup d’autres collectées quotidiennement, valent peu chacun. En manquer un n’est pas grave ; un plafond bas convient.
- Les enregistrements requis, comme une page qui complète un jeu de données qu’un client attend, valent beaucoup plus. Un plafond plus élevé a du sens.
- Les coûts au-delà de la bande passante comptent aussi : le temps, la charge sur la cible et le risque qu’insister sur une page qui échoue entraîne un blocage pour tout le reste.
Sur une API de web scraping qui ne facture que les réponses réussies, le volet financier d’une nouvelle tentative se reporte sur le fournisseur, mais le temps et la charge sur la cible s’appliquent toujours. L’API de Web Scraping de Shifter, par exemple, ne facture pas les requêtes échouées.
Rendre les nouvelles tentatives moins coûteuses
Le plafond n’est que la moitié du problème. L’autre moitié consiste à rendre chaque nouvelle tentative plus susceptible de réussir :
- Attendez avant de réessayer, et attendez plus longtemps à chaque fois. De nombreux échecs sont des limitations de débit temporaires qui se résolvent d’elles-mêmes.
- Changez quelque chose lorsque l’échec le suggère. Un blocage sur une adresse peut réussir depuis une autre ; un délai d’attente dépassé peut nécessiter un délai plus long, pas la même requête à nouveau.
- Ne réessayez pas ce qui ne peut pas réussir. Une erreur 404 ou une erreur d’authentification échouera à chaque fois.
Notre guide sur les nouvelles tentatives et le backoff pour les scrapers web traite ces techniques en détail.
Exécutez-le sur vos propres données
- Exportez, pour une semaine de tâches, la tentative qui a réussi ou un marqueur d’échec.
- Exécutez
attempt_success_ratesavec votre maximum actuel. - Décidez de la valeur d’un succès en nombre de tentatives.
- Fixez le plafond à la valeur renvoyée par
best_cap, par type de tâche s’ils diffèrent. - Revérifiez chaque mois. Les taux évoluent avec les sites et votre configuration.
FAQ
Combien de fois un scraper doit-il réessayer une requête échouée ?
Cela dépend de la vitesse à laquelle votre taux de succès par tentative chute et de la valeur d’un enregistrement réussi. Mesurez le taux de succès de chaque tentative à partir de vos journaux, puis arrêtez-vous à la dernière tentative dont la valeur attendue dépasse encore son coût. Pour de nombreuses tâches, cela représente deux à quatre tentatives.
Pourquoi les nouvelles tentatives réussissent-elles moins souvent que les premières ?
Parce que les échecs ne sont pas aléatoires. Les tâches qui échouent une fois ont plus de chances d’avoir un problème persistant, comme une page manquante ou un blocage, de sorte que chaque tentative ultérieure est tirée de cas plus difficiles.
Les nouvelles tentatives coûtent-elles de l’argent avec des proxies résidentiels ?
Oui. Le trafic des proxies résidentiels est facturé au volume, donc chaque nouvelle tentative télécharge à nouveau la page. Les tentatives échouées qui renvoient de grandes pages d’erreur coûtent aussi de la bande passante.
Dois-je réessayer les erreurs 404 ?
Non. Une page qui n’existe pas n’apparaîtra pas à la tentative suivante. Ne réessayez que les échecs qui peuvent réussir plus tard, comme les délais d’attente dépassés, les limitations de débit et les erreurs serveur temporaires.
En résumé
Réessayer en vaut la peine tant que la chance que la prochaine tentative réussisse, multipliée par la valeur d’un succès, ne tombe pas en dessous du coût de l’essai. Vos journaux contiennent déjà le taux de succès de chaque tentative ; mesurez-le, attribuez une valeur à un succès, et fixez le plafond à partir des chiffres plutôt que de l’habitude.