Comment implémenter des tentatives de requêtes en Python

Commentaires: 0

Le web scraping est une méthode efficace pour extraire des données du web. De nombreux développeurs préfèrent utiliser la bibliothèque de requêtes Python pour réaliser des projets de web scraping, car elle est simple et efficace. Cependant, même si elle est excellente, la bibliothèque de requêtes a ses limites. Un problème typique que l'on peut rencontrer dans le web scraping est l'échec des requêtes, qui conduit souvent à une extraction de données instable. Dans cet article, nous allons voir comment implémenter les tentatives de requêtes en Python, afin que vous puissiez gérer les erreurs HTTP et garder vos scripts de web scraping stables et fiables.

Démarrer avec la bibliothèque de requêtes

Commençons par mettre en place notre environnement. Assurez-vous que Python est installé et que vous avez l'IDE de votre choix. Ensuite, installez la bibliothèque requests si vous ne l'avez pas déjà.

pip install requests

Une fois installé, envoyons une requête à example.com en utilisant le module requests de Python. Voici une fonction simple qui fait exactement cela :

import requests

def send_request(url):
    """
    Envoie une requête HTTP GET à l'URL spécifiée et affiche le code d'état de la réponse.
    
    Parameters:
        url (str): L'URL à laquelle envoyer la demande.
    """
    response = requests.get(url)
    print('Response Status Code: ', response.status_code)

send_request('https://example.com')

La sortie du code est indiquée ci-dessous :

How to implement request retries in Python.png

Examinons de plus près les codes d'état HTTP pour mieux les comprendre.

Comprendre les codes de statut HTTP

Le serveur répond à une requête HTTP par un code d'état indiquant le résultat de la requête. En voici un bref aperçu :

  1. 1xx (Information) : La demande a été reçue et continue d'être traitée.
  2. 2xx (succès) : La demande a été reçue, comprise et acceptée.
    • 200 OK : La demande a abouti. C'est le feu vert des codes d'état HTTP.
  3. 3xx (Redirection) : Une action supplémentaire est nécessaire pour compléter la demande.
  4. 4xx (erreur du client) : Il y a eu une erreur dans la demande, souvent due à un problème côté client.
  5. 5xx (Erreur du serveur) : Le serveur n'a pas pu répondre à une demande valide en raison d'une erreur de sa part.
    • 500 Internal Server Error : Le serveur n'a pas été en mesure de répondre à la demande. Cela indique que le serveur a rencontré une situation inattendue qui l'a empêché de répondre à la demande. C'est l'équivalent du code d'état HTTP du feu rouge.
    • 504 Gateway Timeout : Le serveur n'a pas reçu à temps une réponse du serveur en amont. Il s'agit de l'équivalent du code d'état HTTP du feu de signalisation de la salle d'attente.

Dans notre exemple, le code de statut 200 signifie que la requête vers https://example.com a été complétée. C'est la façon dont le serveur dit : "Tout va bien, votre demande a été acceptée".

Ces codes d'état peuvent également jouer un rôle dans la détection des robots et indiquer si l'accès est restreint en raison d'un comportement similaire à celui d'un robot.

Voici un bref aperçu des codes d'erreur HTTP qui se produisent principalement en raison de problèmes de détection et d'authentification des robots.

  1. 429 too many requests : ce code d'état indique que l'utilisateur a envoyé trop de demandes dans un laps de temps donné ("rate limiting"). Il s'agit d'une réponse courante lorsque les robots dépassent des limites de demandes prédéfinies.
  2. 403 interdit : ce code est renvoyé lorsque le serveur refuse de répondre à la demande. Cela peut se produire si le serveur soupçonne que la demande provient d'un robot, sur la base de l'agent utilisateur ou d'autres critères.
  3. 401 non autorisé : ce statut peut être utilisé si l'accès nécessite une authentification que le bot ne possède pas.
  4. 503 service indisponible : parfois utilisé pour indiquer que le serveur est temporairement incapable de traiter la demande, ce qui peut se produire lors de pics de trafic automatisés.

Mise en place d'un mécanisme de relance en Python

Nous allons maintenant écrire un simple mécanisme de relance en Python pour effectuer une requête HTTP GET à l'aide de la bibliothèque requests. Il arrive que des requêtes réseau échouent en raison d'un problème réseau ou d'une surcharge du serveur. Si notre requête échoue, nous devons donc la relancer.

Mécanisme de base de réessai

La fonction send_request_with_basic_retry_mechanism émet des requêtes HTTP GET vers une URL donnée avec un mécanisme de réessai basique en place qui ne réessaie que si une exception de réseau ou de requête telle qu'une erreur de connexion est rencontrée. La requête sera relancée un maximum de fois. Si toutes les tentatives échouent avec une telle exception, il lève la dernière exception rencontrée.

import requests
import time

def send_request_with_basic_retry_mechanism(url, max_retries=2):
    """
    Envoie une requête HTTP GET à une URL avec un mécanisme de relance de base.
    
    Parameters:
        url (str): L'URL à laquelle envoyer la demande.
        max_retries (int): Nombre maximal de tentatives de réitération de la demande.

    Raises:
        requests.RequestException: Lève la dernière exception si toutes les tentatives échouent.

    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            print('Response status: ', response.status_code)
            break  # Sortir de la boucle si la demande est acceptée
        except requests.RequestException as error:
            print(f"Attempt {attempt+1} failed:", error)
            if attempt < max_retries - 1:
                print(f"Retrying...")
                time.sleep(delay)  # Attendre avant de réessayer
            else:
                print("Max retries exceeded.")
                # Relance la dernière exception si le nombre maximum de tentatives est atteint
                raise
                send_request_with_basic_retry_mechanism('https://example.com')

Mécanisme de relance avancée

Adaptons maintenant le mécanisme de relance de base pour gérer les scénarios dans lesquels le site web que nous essayons de récupérer met en œuvre des mécanismes de détection des robots qui peuvent entraîner un blocage. Dans ce cas, nous devons réessayer la requête plusieurs fois, car il peut s'agir non seulement de blocages dus à la détection de robots, mais aussi de problèmes de réseau ou de serveur.

La fonction ci-dessous send_request_with_advance_retry_mechanism envoie une requête HTTP GET à l'URL fournie avec des tentatives de réessai et un délai de réessai optionnels. Elle tente d'envoyer la requête plusieurs fois pour le nombre de tentatives spécifié et affiche le code d'état de la réponse si la requête obtient une réponse. S'il rencontre une erreur au cours de l'opération de requête, il affiche le message d'erreur et recommence. Il attend le délai de réessai spécifié entre chaque tentative. Si la demande échoue même après le nombre de tentatives spécifié, il lève la dernière exception rencontrée.

Le paramètre de délai est important car il permet d'éviter de bombarder le serveur de multiples demandes à intervalles rapprochés. Au contraire, il attend que le serveur ait suffisamment de temps pour traiter la demande, ce qui lui fait penser que c'est un humain et non un robot qui fait les demandes. Le mécanisme de relance doit donc être retardé afin d'éviter une surcharge du serveur ou une réponse lente du serveur, ce qui pourrait déclencher des mécanismes anti-bots.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1):
    """
    Envoie une requête HTTP GET à l'URL spécifiée avec un mécanisme de relance avancé.
    
    Parameters:
        url (str): L'URL à laquelle envoyer la demande.
        max_retries (int): Nombre maximal de tentatives de réitération de la demande. La valeur par défaut est 3.
        delay (int): Délai (en secondes) entre les tentatives. La valeur par défaut est 1.

    Raises:
        requests.RequestException: Lève la dernière exception si toutes les tentatives échouent.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Lever une exception pour les codes d'état 4xx ou 5xx
            response.raise_for_status()
            print('Response Status Code:', response.status_code)
        except requests.RequestException as e:
            # Imprimer le message d'erreur et le numéro de la tentative si la demande échoue
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                # Imprimer le message de réessai et attendre avant de réessayer
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                # Si le nombre maximum de tentatives est dépassé, imprimer le message et relancer l'exception
                print("Max retries exceeded.")
                raise

# Exemple d'utilisation
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Voici les inconvénients de cette mise en œuvre :

  • Tous les codes d'état appartenant aux gammes 4xx et 5xx sont retentés. Toutefois, les demandes qui aboutissent à un code d'état 404 (Not Found) n'ont pas besoin d'être relancées.
  • Certains services de détection des robots peuvent répondre avec un code d'état 200 (OK), mais le contenu de la réponse peut être différent. Cette situation n'est pas gérée dans l'implémentation actuelle. La mise en œuvre d'une validation de la longueur du contenu pourrait résoudre ce problème.

Voici le code corrigé ainsi que les commentaires concernant les inconvénients :

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    Envoie une requête HTTP GET à l'URL spécifiée avec un mécanisme de relance avancé.

    Parameters:
        url (str): L'URL à laquelle envoyer la demande.
        max_retries (int): Nombre maximal de tentatives de réitération de la demande. La valeur par défaut est 3.
        delay (int): Délai (en secondes) entre les tentatives. La valeur par défaut est 1.
        min_content_length (int): Longueur minimale du contenu de la réponse à considérer comme valide. La valeur par défaut est 10.

    Raises:
        requests.RequestException: Lève la dernière exception si toutes les tentatives échouent.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Lever une exception pour les codes d'état 4xx ou 5xx
            response.raise_for_status()
            
            # Vérifier si le code d'état de la réponse est 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Sortir de la boucle pour les erreurs 404
            
            # Vérifier si la longueur du texte de la réponse est inférieure à la longueur minimale spécifiée du contenu
            if len(response.text) < min_content_length:
                print("Response text length is less than specified minimum. Retrying...")
                time.sleep(delay)
                continue  # Réessayer la demande
            
            print('Response Status Code:', response.status_code)
            # Si les conditions sont remplies, sortir de la boucle
            break
            
        except requests.RequestException as e:
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                print("Max retries exceeded.")
                # Relance la dernière exception si le nombre maximum de tentatives est atteint
                raise

# Exemple d'utilisation
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Gestion des erreurs HTTP spécifiques avec les serveurs mandataires

Pour certaines erreurs telles que 429 Too Many Requests, l'utilisation de proxies rotatifs peut aider à répartir les demandes et à éviter la limitation du débit.

Le code ci-dessous implémente une stratégie de relance avancée avec l'utilisation de proxies. De cette manière, nous pouvons mettre en place un mécanisme de relance des requêtes Python. Il est également important d'utiliser des proxys de web scraping de haute qualité. Ces proxys doivent disposer d'un bon algorithme de rotation des proxys et d'un pool fiable.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    Envoie une requête HTTP GET à l'URL spécifiée avec un mécanisme de relance avancé.

    Parameters:
        url (str): L'URL à laquelle envoyer la demande.
        max_retries (int): Nombre maximal de tentatives de réitération de la demande. La valeur par défaut est 3.
        delay (int): Délai (en secondes) entre les tentatives. La valeur par défaut est 1.
   
    Raises:
        requests.RequestException: Lève la dernière exception si toutes les tentatives échouent.
    """
    
    proxies = {
        "http": "http://USER:PASS@HOST:PORT",
        "https": "https://USER:PASS@HOST:PORT"
    }
    
    for attempt in range(max_retries):
        try:
            response = requests.get(url, proxies=proxies, verify=False)
            # Lever une exception pour les codes d'état 4xx ou 5xx
            response.raise_for_status()
            
            # Vérifier si le code d'état de la réponse est 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break   # Sortir de la boucle pour les erreurs 404
            
            # Vérifier si la longueur du texte de la réponse est inférieure à 10 caractères
            if len(response.text) < min_content_length:
                print("Response text length is less than 10 characters. Retrying...")
                time.sleep(delay)
                continue  # Réessayer la demande
            
            print('Response Status Code:', response.status_code)
            # Si les conditions sont remplies, sortir de la boucle
            break
            
        except requests.RequestException as e:
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                print("Max retries exceeded.")
                # Relance la dernière exception si le nombre maximum de tentatives est atteint
                raise

send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Les tentatives de requêtes en Python sont cruciales pour un web scraping efficace. Les méthodes que nous avons examinées pour gérer les tentatives peuvent aider à prévenir le blocage et à améliorer l'efficacité et la fiabilité de la collecte de données. La mise en œuvre de ces techniques rendra vos scripts de web scraping plus robustes et moins susceptibles d'être détectés par les systèmes de protection contre les robots.

Commentaires:

0 Commentaires