Come implementare i tentativi di richiesta in Python

Commenti: 0

Il web scraping è un metodo efficace per estrarre dati dal web. Molti sviluppatori preferiscono usare la libreria request di Python per realizzare progetti di web scraping, perché è semplice ed efficace. Tuttavia, per quanto ottima, la libreria request ha i suoi limiti. Un problema tipico che si può incontrare nello scraping del web sono le richieste fallite, che spesso portano a un'estrazione instabile dei dati. In questo articolo, vedremo come implementare i tentativi di richiesta in Python, in modo da poter gestire gli errori HTTP e mantenere gli script di web scraping stabili e affidabili.

Iniziare con la libreria delle richieste

Per prima cosa configuriamo il nostro ambiente. Assicuratevi di avere installato Python e un IDE di vostra scelta. Quindi installate la libreria requests, se non l'avete già.

pip install requests

Una volta installato, inviamo una richiesta a example.com utilizzando il modulo requests di Python. Ecco una semplice funzione che fa proprio questo:

import requests

def send_request(url):
    """
    Invia una richiesta HTTP GET all'URL specificato e stampa il codice di stato della risposta.
    
    Parameters:
        url (str): L'URL a cui inviare la richiesta.
    """
    response = requests.get(url)
    print('Response Status Code: ', response.status_code)

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

L'output del codice è mostrato di seguito:

How to implement request retries in Python.png

Diamo un'occhiata più da vicino ai codici di stato HTTP per comprenderli meglio.

Comprendere i codici di stato HTTP

Il server risponde a una richiesta HTTP con un codice di stato che indica l'esito della richiesta. Ecco un rapido riepilogo:

  1. 1xx (informativo): La richiesta è stata ricevuta e continua a essere elaborata.
  2. 2xx (successo): La richiesta è stata ricevuta, compresa e accettata.
    • 200 OK: La richiesta è andata a buon fine. Questo è il semaforo verde dei codici di stato HTTP.
  3. 3xx (reindirizzamento): È necessaria un'ulteriore azione per completare la richiesta.
  4. 4xx (errore del client): Si è verificato un errore nella richiesta, spesso dovuto a qualcosa sul lato client.
  5. 5xx (errore del server): Il server non è riuscito a soddisfare una richiesta valida a causa di un errore del server stesso.
    • 500 Errore interno del server: Il server non è stato in grado di completare la richiesta. Ciò indica che il server ha incontrato una condizione inaspettata che gli ha impedito di soddisfare la richiesta. È l'equivalente del codice di stato HTTP del semaforo rosso.
    • 504 Timeout del gateway: Il server non ha ricevuto una risposta dal server a monte in tempo utile. Questo è il codice di stato HTTP equivalente al semaforo del timeout della sala d'attesa.

Nel nostro esempio, il codice di stato 200 significa che la richiesta a https://example.com è stata completata. È il modo in cui il server dice: "È tutto a posto, la richiesta è andata a buon fine".

Questi codici di stato possono anche svolgere un ruolo nel rilevamento dei bot e indicare quando l'accesso è limitato a causa di un comportamento simile a quello dei bot.

Di seguito è riportata una rapida carrellata di codici di errore HTTP che si verificano principalmente a causa di problemi di rilevamento e autenticazione dei bot.

  1. 429 troppe richieste: questo codice di stato indica che l'utente ha inviato troppe richieste in un determinato periodo di tempo ("rate limiting"). È una risposta comune quando i bot superano i limiti di richiesta predefiniti.
  2. 403 forbidden: questo codice viene restituito quando il server rifiuta di soddisfare la richiesta. Questo può accadere se il server sospetta che la richiesta provenga da un bot, in base all'User-Agent o ad altri criteri.
  3. 401 non autorizzato: questo stato può essere utilizzato se l'accesso richiede un'autenticazione che il bot non possiede.
  4. 503 servizio non disponibile: a volte viene utilizzato per indicare che il server non è temporaneamente in grado di gestire la richiesta, cosa che può accadere durante i picchi di traffico automatico.

Implementazione del meccanismo di retry in Python

Scriviamo ora un semplice meccanismo di retry in Python per effettuare richieste HTTP GET con la libreria requests. A volte le richieste di rete falliscono a causa di qualche problema di rete o del sovraccarico del server. Quindi, se la nostra richiesta non va a buon fine, dobbiamo riprovare.

Meccanismo di base di ripetizione

La funzione send_request_with_basic_retry_mechanism effettua richieste HTTP GET a un determinato URL con un meccanismo di retry di base, che viene riprovato solo se si verifica un'eccezione di rete o di richiesta, come un errore di connessione. Riproverebbe la richiesta per un massimo di max_tentativi. Se tutti i tentativi falliscono con un'eccezione, viene sollevata l'ultima eccezione incontrata.

import requests
import time

def send_request_with_basic_retry_mechanism(url, max_retries=2):
    """
    Invia una richiesta HTTP GET a un URL con un meccanismo di riprova di base.
    
    Parameters:
        url (str): L'URL a cui inviare la richiesta.
        max_retries (int): Il numero massimo di volte in cui riprovare la richiesta.

    Raises:
        requests.RequestException: Solleva l'ultima eccezione se tutti i tentativi falliscono.

    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            print('Response status: ', response.status_code)
            break  # Esce dal ciclo se la richiesta è andata a buon fine
        except requests.RequestException as error:
            print(f"Attempt {attempt+1} failed:", error)
            if attempt < max_retries - 1:
                print(f"Retrying...")
                time.sleep(delay)  # Aspettare prima di riprovare
            else:
                print("Max retries exceeded.")
                # Rilanciare l'ultima eccezione se il massimo dei tentativi è stato raggiunto
                raise
                send_request_with_basic_retry_mechanism('https://example.com')

Meccanismo di ripetizione anticipata

Adattiamo ora il meccanismo di base di retry per gestire gli scenari in cui il sito web che stiamo cercando di scrappare implementa meccanismi di rilevamento dei bot che possono risultare in un blocco. Per affrontare tali scenari, è necessario riprovare la richiesta più volte, poiché i blocchi potrebbero non essere solo dovuti al rilevamento dei bot, ma anche a problemi di rete o di server.

Adattiamo ora il meccanismo di base di retry per gestire gli scenari in cui il sito web che stiamo cercando di scrappare implementa meccanismi di rilevamento dei bot che possono risultare in un blocco. Per affrontare tali scenari, è necessario riprovare la richiesta più volte, poiché i blocchi potrebbero non essere solo dovuti al rilevamento dei bot, ma anche a problemi di rete o di server.

Il parametro del ritardo è importante perché evita di bombardare il server con più richieste a distanza ravvicinata. Al contrario, attende che il server abbia il tempo sufficiente per elaborare la richiesta, facendo credere al server che sia un umano e non un bot a effettuare le richieste. Pertanto, il meccanismo di riprova dovrebbe essere ritardato per evitare il sovraccarico del server o una risposta lenta del server che potrebbe innescare meccanismi anti-bot.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1):
    """
    Invia una richiesta HTTP GET all'URL specificato con un meccanismo di riprova avanzato.
    
    Parameters:
        url (str): L'URL a cui inviare la richiesta.
        max_retries (int): Il numero massimo di volte in cui riprovare la richiesta. L'impostazione predefinita è 3.
        delay (int): Il ritardo (in secondi) tra i tentativi. L'impostazione predefinita è 1.

    Raises:
        requests.RequestException: Solleva l'ultima eccezione se tutti i tentativi falliscono.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Raise an exception for 4xx or 5xx status codes
            response.raise_for_status()
            print('Response Status Code:', response.status_code)
        except requests.RequestException as e:
            # Stampa il messaggio di errore e il numero di tentativo se la richiesta fallisce
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                # Stampa il messaggio di ripetizione e aspetta prima di riprovare
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                # Se il numero massimo di tentativi viene superato, stampare il messaggio e rilanciare l'eccezione
                print("Max retries exceeded.")
                raise

# Esempio di utilizzo
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Ecco gli svantaggi di questa implementazione:

  • Tutti i codici di stato appartenenti agli intervalli 4xx e 5xx vengono riproposti. Tuttavia, le richieste che risultano in un codice di stato 404 (Not Found) non devono essere ritentate.
  • Alcuni servizi di rilevamento bot possono rispondere con un codice di stato 200 (OK), ma il contenuto della risposta può essere diverso. Questa situazione non è gestita nell'implementazione attuale. L'implementazione della convalida della lunghezza del contenuto potrebbe risolvere questo problema.

Ecco il codice corretto e i commenti che affrontano gli inconvenienti:

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    Invia una richiesta HTTP GET all'URL specificato con un meccanismo di riprova avanzato.

    Parameters:
        url (str): L'URL a cui inviare la richiesta.
        max_retries (int): Il numero massimo di tentativi della richiesta. L'impostazione predefinita è 3.
        delay (int): Il ritardo (in secondi) tra i tentativi. L'impostazione predefinita è 1.
        min_content_length (int): La lunghezza minima del contenuto della risposta da considerare valida. L'impostazione predefinita è 10.

    Raises:
        requests.RequestException: Solleva l'ultima eccezione se tutti i tentativi falliscono.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Sollevare un'eccezione per i codici di stato 4xx o 5xx
            response.raise_for_status()
            
            # Controlla se il codice di stato della risposta è 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Esce dal ciclo per gli errori 404
            
            # Controlla se la lunghezza del testo della risposta è inferiore alla lunghezza minima del contenuto specificata
            if len(response.text) < min_content_length:
                print("Response text length is less than specified minimum. Retrying...")
                time.sleep(delay)
                continue  # Riprova la richiesta
            
            print('Response Status Code:', response.status_code)
            # Se le condizioni sono soddisfatte, uscire dal ciclo
            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.")
                # Rilanciare l'ultima eccezione se il massimo dei tentativi è stato raggiunto
                raise

# Esempio di utilizzo
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Gestione di errori HTTP specifici con i proxy

Per alcuni errori, come 429 Troppe richieste, l'uso di proxy a rotazione può aiutare a distribuire le richieste ed evitare la limitazione della velocità.

Il codice seguente implementa una strategia di retry avanzata insieme all'uso dei proxy. In questo modo, possiamo implementare un meccanismo di retry per le richieste di Python. Anche l'uso di proxy di alta qualità per il web scraping è importante. Questi proxy devono avere un buon algoritmo di rotazione dei proxy e un pool affidabile.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    Invia una richiesta HTTP GET all'URL specificato con un meccanismo di riprova avanzato.

    Parameters:
        url (str): L'URL a cui inviare la richiesta.
        max_retries (int): Il numero massimo di volte in cui riprovare la richiesta. L'impostazione predefinita è 3.
        delay (int): Il ritardo (in secondi) tra i tentativi. L'impostazione predefinita è 1.
   
    Raises:
        requests.RequestException: Solleva l'ultima eccezione se tutti i tentativi falliscono.
    """
    
    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)
            # Sollevare un'eccezione per i codici di stato 4xx o 5xx
            response.raise_for_status()
            
            # Controllare se il codice di stato della risposta è 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Esce dal ciclo per gli errori 404
            
            # Verifica se la lunghezza del testo di risposta è inferiore a 10 caratteri
            if len(response.text) < min_content_length:
                print("Response text length is less than 10 characters. Retrying...")
                time.sleep(delay)
                continue  # Riprova la richiesta
            
            print('Response Status Code:', response.status_code)
            # Se le condizioni sono soddisfatte, uscire dal ciclo
            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.")
                # Rilanciare l'ultima eccezione se il massimo dei tentativi è stato raggiunto
                raise

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

I tentativi di risposta alle richieste in Python sono fondamentali per un web scraping efficace. I metodi che abbiamo discusso per gestire i ritentativi possono aiutare a prevenire il blocco e a migliorare l'efficienza e l'affidabilità della raccolta dei dati. L'implementazione di queste tecniche renderà i vostri script di web scraping più robusti e meno suscettibili di essere rilevati dai sistemi di protezione dei bot.

Commenti:

0 Commenti