Gérer les requêtes qui échouent en Python

Apprenez à gérer les requêtes HTTP qui échouent en Python avec des stratégies de relance efficaces et une logique personnalisée.
9 min de lecture
Managing Failed Requests in Python blog image

Lorsque l’on utilise le protocole HTTP, les requêtes qui échouent sont une réalité inévitable à laquelle il faut faire face. Dans le domaine du développement web, un statut 200 indique une bonne réponse. Cependant, nous n’obtenons pas toujours un 200, et ce guide vous aidera à comprendre comment gérer ces codes d’état autres que 200.

Selon Mozilla, les codes de statut peuvent être répartis dans les catégories suivantes :

  • 100-199: Réponses informatives
  • 200-299: Réponses positives
  • 300-399: Messages de redirection
  • 400-499: Messages d’erreur du client
  • 500-599: Messages d’erreur du serveur

Que sont les codes d’état ?

Les codes d’erreur sont importants. Lors de la création de programmes côté client tels que les scanners de sites web, nous devons principalement nous concentrer sur les codes d’état de l’ordre de 400+ et 500+. Les codes dans les 400 couvrent généralement les erreurs côté client telles que les problèmes d’authentification, les limitations de débit, les dépassements de délai et la tristement célèbre erreur 404 : Fichier introuvable. Les codes de plus de 500 correspondent généralement à des problèmes de serveur.

Depuis des décennies, Mozilla documente les normes de développement web du W3C et de l’IETF. Vous trouverez ci-dessous une liste de codes d’erreur courants que vous pourriez rencontrer. Cette liste n’est pas exhaustive. Ces erreurs proviennent de la documentation officielle de Mozilla. En fonction de votre site cible, vos codes peuvent différer légèrement, mais la logique devrait rester la même.

Code de statut Signification Description
400 Mauvaise demande Vérifiez le format de votre demande
401 Non autorisé Vérifiez votre clé API
403 Interdit Vous ne pouvez pas accéder à ces données
404 Non trouvé Le site ou le point de terminaison n’existe pas
408 Délai d’attente de la demande La demande a expiré, réessayez
429 Trop de demandes Ralentissez vos demandes
500 Erreur de serveur interne Erreur générique du serveur, demande de réessai
501 Non mise en œuvre Le serveur ne prend pas encore en charge cette fonction
502 Mauvaise passerelle Échec de la réponse d’un serveur en amont
503 Service indisponible Le serveur est temporairement hors service, réessayer plus tard
504 Délai d’attente de la passerelle Le délai d’attente d’un serveur en amont a expiré

Stratégies de réessai

Lors de la mise en œuvre d’un mécanisme de relance, vous pouvez utiliser des bibliothèques prédéfinies telles que HTTPAdapter et Tenacity. Selon le cas, vous pouvez même écrire votre propre logique de relance.

En règle générale, nous voulons une limite de tentatives et une stratégie pour revenir en arrière. Nous avons besoin d’une limite pour ne pas être pris dans une boucle infinie de tentatives. Nous devons nous retirer petit à petit afin de respecter le serveur hôte. Lorsque vos demandes arrivent trop vite, elles vous bloquent ou submergent le serveur.

  • Limites de réessais: Vous devez fixer une limite. Après X tentatives, votre scraper abandonnera.
  • Algorithme de backoff: Celui-ci est relativement simple. Il s’agit de commencer par un petit backoff et de l’augmenter à chaque nouvel essai. Nous voulons commencer par 0,3, puis passer à 0,6, 1,2 et ainsi de suite.

Nous voulons réessayer nos demandes jusqu’à une certaine limite. Après chaque échec, nous voulons attendre un peu plus longtemps.

Adaptateur HTTPA

Avec HTTPAdapter, nous devons configurer trois choses : total, backoff_factor, et status_forcelist. allowed_methods n’est pas vraiment une exigence, mais il rend notre code plus sûr en aidant à définir nos conditions de réessai. Dans le code ci-dessous, nous utilisons httpbin pour forcer automatiquement une erreur et déclencher notre logique de réessai.

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

#create a session
session = requests.Session()

#configure retry settings
retry = Retry(
    total=3,                  #maximum retries
    backoff_factor=0.3,       #time between retries (exponential backoff)
    status_forcelist=(429, 500, 502, 503, 504), #status codes to trigger a retry
    allowed_methods={"GET", "POST"}
)

#mount the adapter with our custom settings
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)

#actually make a request with our retry logic
try:
    print("Making a request with retry logic...")
    response = session.get("https://httpbin.org/status/500")
    response.raise_for_status()
    print("✅ Request successful:", response.status_code)
except requests.exceptions.RequestException as e:
    print("❌ Request failed after retries:", e)

Une fois l’objet Session créé, nous procédons comme suit :

  • Créez un objet Retry et définissez les éléments suivants :
    • total: limite maximale pour la relance d’une demande.
    • backoff_factor: Temps d’attente entre les tentatives. Ce facteur s’ajuste de manière exponentielle au fur et à mesure que le nombre de tentatives augmente.
    • status_forcelist: Une liste de mauvais codes d’état. Tout code figurant dans cette liste déclenchera automatiquement une nouvelle tentative.
  • Créer un objet HTTPAdapter avec notre variable retry: adapter = HTTPAdapter(max_retries=retry).
  • Une fois l’adaptateur créé, nous le montons sur les méthodes HTTP et HTTPS à l’aide de session.mount().

Lorsque vous exécutez ce code, nos trois tentatives(total=3) s’exécutent et vous obtenez le résultat suivant.

Making a request with retry logic...
❌ Request failed after retries: HTTPSConnectionPool(host='httpbin.org', port=443): Max retries exceeded with url: /status/500 (Caused by ResponseError('too many 500 error responses'))

Ténacité

Vous pouvez également utiliser Tenacity, une bibliothèque de relance open source populaire pour Python. Elle n’est pas limitée à HTTP, mais elle nous offre un moyen expressif et compréhensible d’implémenter les tentatives.

Vous devez d’abord l’installer.

pip install tenacity

Une fois installé, nous créons un décorateur et l’utilisons pour envelopper une fonction de requête. Avec notre décorateur @retry, nous ajoutons les arguments stop, wait et retry.

import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type, RetryError

#define a retry strategy
@retry(
    stop=stop_after_attempt(3),  #retry up to 3 times
    wait=wait_exponential(multiplier=0.3),  #exponential backoff
    retry=retry_if_exception_type(requests.exceptions.RequestException),  #retry on request failures
)

def make_request():
    print("Making a request with retry logic...")
    response = requests.get("https://httpbin.org/status/500")
    response.raise_for_status()
    print("✅ Request successful:", response.status_code)
    return response

# Attempt to make the request
try:
    make_request()
except RetryError as e:
    print("❌ Request failed after all retries:", e)

La logique et les paramètres sont très similaires à notre premier exemple avec HTTPAdapter.

  • stop=stop_after_attempt(3): Ceci indique à tenacity d’abandonner après 3 tentatives infructueuses.
  • wait=wait_exponential(multiplier=0.3) utilise la même attente que précédemment. Il recule également de manière exponentielle, comme précédemment.
  • retry=retry_if_exception_type(requests.exceptions.RequestException) indique à tenacity d’utiliser cette logique à chaque fois qu’une RequestException se produit.
  • make_request() effectue une requête vers notre point d’accès à l’erreur. Elle reçoit tous les traits du décorateur que nous avons créé au-dessus d’elle.

Lorsque vous exécutez ce code, vous obtenez un résultat similaire.

Making a request with retry logic...
Making a request with retry logic...
Making a request with retry logic...
❌ Request failed after all retries: RetryError[<Future at 0x75e762970760 state=finished raised HTTPError>]

Construisez votre propre mécanisme de réessai

Vous pouvez également créer votre propre mécanisme de relance. Lorsqu’il s’agit de code personnalisé, c’est souvent la meilleure approche. Avec une quantité de code relativement faible, nous pouvons obtenir le même effet que celui obtenu avec ces bibliothèques.

Dans le code ci-dessous, nous devons importer sleep pour notre backoff exponentiel. Nous définissons à nouveau notre configuration : total, backoff_factor et bad_codes. Nous utilisons ensuite une boucle while pour maintenir notre logique de réessai. Tant que nous avons encore des essais et que nous n’avons pas réussi, nous tentons la requête.

import requests
from time import sleep

#create a session
session = requests.Session()

#define our retry settings
total = 3
backoff_factor = 0.3
bad_codes = [429, 500, 502, 503, 504]

#try counter and success boolean
current_tries = 0
success = False

#attempt until we succeed or run out of tries
while current_tries < total and not success:
    try:
        print("Making a request with retry logic...")
        response = session.get("https://httpbin.org/status/500")
        if response.status_code in bad_codes:
            raise requests.exceptions.HTTPError(f"Received {response.status_code}, triggering retry")
        print("✅ Request successful:", response.status_code)
        success = True
    except requests.exceptions.RequestException as e:
        print(f"❌ Request failed: {e}, retries left: {total-current_tries}")
        sleep(backoff_factor)
        backoff_factor = backoff_factor * 2
        current_tries+=1

La logique réelle est ici gérée par une simple boucle while.

  • Si response.status_code fait partie de notre liste de bad_codes, nous levons une exception.
  • Si une demande n’aboutit pas, nous
    • Imprime un message d’erreur sur la console.
    • sleep(backoff_factor) attend avant d’envoyer la demande suivante.
    • backoff_factor = backoff_factor * 2 double notre backoff_factor pour le prochain essai.
    • Nous incrémentons current_tries pour ne pas rester indéfiniment dans la boucle.

Voici la sortie de notre logique de réessai personnalisée.

Making a request with retry logic...
❌ Request failed: Received 500, triggering retry, retries left: 3
Making a request with retry logic...
❌ Request failed: Received 500, triggering retry, retries left: 2
Making a request with retry logic...
❌ Request failed: Received 500, triggering retry, retries left: 1

Dépasser les blocages

Dans la nature, certains sites vont vous bloquer. La meilleure pratique consiste à toujours utiliser un proxy pour les requêtes Python. Avec un proxy, votre demande passe par une machine différente. Cela permet de protéger votre identité et d’éviter que votre adresse IP ne soit bloquée par votre site cible. Nous disposons même d’un guide détaillé sur la manière de contourner les blocages d’IP. Nos proxys résidentiels sont conçus pour vous permettre de surmonter ces difficultés.

Conclusion

Vous savez maintenant comment gérer les échecs des requêtes HTTP en Python. Que vous écriviez un scraper, un client API ou des outils d’automatisation, vous savez comment gérer ces problèmes. Pour éviter toutes sortes d’échecs de requêtes, nous avons développé des produits tels que l’API Web Unlocker et le Scraping Browser. Ces outils gèrent automatiquement les mesures anti-bots, les défis CAPTCHA et les blocages d’IP, garantissant ainsi un scrapping web efficace et sans faille, même pour les sites web les plus difficiles.

Inscrivez-vous maintenant et commencez votre essai gratuit dès aujourd’hui.