Como implementar novas tentativas de pedidos em Python

Comentários: 0

O Web scraping é um método eficaz de extração de dados da Web. Muitos programadores preferem utilizar a biblioteca de pedidos Python para realizar projectos de recolha de dados da Web, uma vez que é simples e eficaz. No entanto, por muito boa que seja, a biblioteca de pedidos tem as suas limitações. Um problema típico que podemos encontrar no web scraping são os pedidos falhados, que muitas vezes levam a uma extração de dados instável. Neste artigo, vamos passar pelo processo de implementação de novas tentativas de pedidos em Python, para que possa lidar com os erros HTTP e manter os seus scripts de recolha de dados da Web estáveis e fiáveis.

Começando a usar a biblioteca de solicitações

Primeiro, vamos configurar o nosso ambiente. Certifique-se de que tem o Python instalado e qualquer IDE da sua escolha. Em seguida, instale a biblioteca requests, se ainda não a tiver.

pip install requests

Uma vez instalado, vamos enviar um pedido para example.com usando o módulo requests do Python. Aqui está uma função simples que faz exatamente isso:

import requests

def send_request(url):
    """
    Envia um pedido HTTP GET para o URL especificado e imprime o código de estado da resposta.
    
    Parameters:
        url (str): O URL para o qual enviar o pedido.
    """
    response = requests.get(url)
    print('Response Status Code: ', response.status_code)

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

A saída do código é mostrada abaixo:

How to implement request retries in Python.png

Vamos analisar mais detalhadamente os códigos de estado HTTP para os compreender melhor.

Compreender os códigos de estado HTTP

O servidor responde a um pedido HTTP com um código de estado que indica o resultado do pedido. Eis um resumo rápido:

  1. 1xx (Informativo): O pedido foi recebido e continua a ser processado.
  2. 2xx (Sucesso): O pedido foi recebido, compreendido e aceite.
    • 200 OK: O pedido foi bem sucedido. Esta é a luz verde dos códigos de estado HTTP.
  3. 3xx (Redireccionamento): É necessária uma ação adicional para concluir a solicitação.
  4. 4xx (Erro do cliente): Houve um erro com a solicitação, geralmente devido a algo no lado do cliente.
  5. 5xx (Erro do servidor): O servidor não conseguiu atender a uma solicitação válida devido a um erro em sua extremidade.
    • Erro interno do servidor 500: O servidor não conseguiu concluir o pedido. Isto indica que o servidor se deparou com uma condição inesperada que o impediu de satisfazer o pedido. Este é o código de estado HTTP equivalente ao semáforo vermelho.
    • 504 Tempo limite do gateway: O servidor não recebeu uma resposta do servidor a montante a tempo. Este é o código de estado HTTP equivalente ao semáforo de tempo limite da sala de espera.

No nosso exemplo, o código de estado 200 significa que o pedido para https://example.com foi concluído. É a forma de o servidor dizer: "Está tudo bem aqui, o seu pedido foi um sucesso".

Estes códigos de estado podem também desempenhar um papel na deteção de bots e indicar quando o acesso é restringido devido a um comportamento semelhante ao de um bot.

Segue-se um rápido resumo dos códigos de erro HTTP que ocorrem principalmente devido a problemas de deteção e autenticação de bots.

  1. 429 Demasiados pedidos: este código de estado indica que o utilizador enviou demasiados pedidos num determinado período de tempo ("rate limiting"). É uma resposta comum quando os bots excedem os limites de solicitação predefinidos.
  2. 403 proibido: esse código é retornado quando o servidor se recusa a atender a solicitação. Isso pode ocorrer se o servidor suspeitar que a solicitação é proveniente de um bot, com base no User-Agent ou em outros critérios.
  3. 401 não autorizado: este status pode ser usado se o acesso requer autenticação que o bot não tem.
  4. 503 serviço indisponível: às vezes usado para indicar que o servidor está temporariamente incapaz de lidar com a solicitação, o que pode acontecer durante picos de tráfego automatizados.

Implementando o mecanismo de repetição em Python

Vamos agora escrever um mecanismo simples de repetição em Python para efetuar um pedido HTTP GET com a biblioteca requests. Há alturas em que os pedidos de rede falham devido a algum problema de rede ou sobrecarga do servidor. Assim, se o nosso pedido falhar, devemos tentar novamente estes pedidos.

Mecanismo básico de repetição

A função send_request_with_basic_retry_mechanism efectua pedidos HTTP GET a um determinado URL com um mecanismo básico de repetição que só repetirá se for encontrada uma exceção de rede ou de pedido, como um erro de ligação. O pedido será repetido no máximo max_retries vezes. Se todas as tentativas falharem com essa exceção, é levantada a última exceção encontrada.

import requests
import time

def send_request_with_basic_retry_mechanism(url, max_retries=2):
    """
    Envia um pedido HTTP GET para um URL com um mecanismo básico de repetição.
    
    Parameters:
        url (str): O URL para o qual enviar o pedido.
        max_retries (int): O número máximo de tentativas para repetir o pedido.

    Raises:
        requests.RequestException: Levanta a última exceção se todas as tentativas falharem.

    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            print('Response status: ', response.status_code)
            break  # Sair do ciclo se o pedido for bem sucedido
        except requests.RequestException as error:
            print(f"Attempt {attempt+1} failed:", error)
            if attempt < max_retries - 1:
                print(f"Retrying...")
                time.sleep(delay)  # Esperar antes de tentar de novo
            else:
                print("Max retries exceeded.")
                # Aumentar novamente a última exceção se o número máximo de tentativas for atingido
                raise
                send_request_with_basic_retry_mechanism('https://example.com')

Mecanismo de repetição de avanço

Vamos agora adaptar o mecanismo básico de repetição para lidar com cenários em que o sítio Web que estamos a tentar extrair implementa mecanismos de deteção de bots que podem resultar em bloqueio. Para lidar com esses cenários, precisamos de repetir o pedido diligentemente várias vezes, uma vez que podem não ser apenas bloqueios de deteção de bots, mas também podem ser devidos a problemas de rede ou de servidor.

A função abaixo, send_request_with_advance_retry_mechanism, envia um pedido HTTP GET para o URL fornecido com tentativas de repetição opcionais e atraso de repetição. Tenta enviar o pedido várias vezes para o número de tentativas especificado e imprime o código de estado da resposta se o pedido obtiver a resposta com êxito. Se encontrar um erro durante a operação do pedido, imprime a mensagem de erro e volta a tentar. Aguarda o atraso de repetição especificado entre cada tentativa. Se o pedido falhar mesmo após o número especificado de tentativas de repetição, levanta a última exceção encontrada.

O parâmetro de atraso é importante porque evita bombardear o servidor com vários pedidos num intervalo próximo. Em vez disso, espera que o servidor tenha tempo suficiente para processar o pedido, fazendo com que o servidor pense que é um humano e não um bot que está a fazer os pedidos. Assim, o mecanismo de repetição deve ser atrasado para evitar a sobrecarga do servidor ou a lentidão da resposta do servidor, o que pode acionar mecanismos anti-bot.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1):
    """
    Envia um pedido HTTP GET para o URL especificado com um mecanismo avançado de repetição.
    
    Parameters:
        url (str): O URL para o qual enviar o pedido.
        max_retries (int): O número máximo de tentativas para repetir o pedido. A predefinição é 3.
        delay (int): O atraso (em segundos) entre novas tentativas. A predefinição é 1.

    Raises:
        requests.RequestException: Levanta a última exceção se todas as tentativas falharem.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Levantar uma exceção para códigos de estado 4xx ou 5xx
            response.raise_for_status()
            print('Response Status Code:', response.status_code)
        except requests.RequestException as e:
            # Imprimir mensagem de erro e número da tentativa se o pedido falhar
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                # Imprimir a mensagem de nova tentativa e aguardar antes de tentar novamente
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                # Se o número máximo de tentativas for excedido, imprimir a mensagem e voltar a aumentar a exceção
                print("Max retries exceeded.")
                raise

# Exemplo de utilização
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Eis os inconvenientes desta aplicação:

  • Todos os códigos de estado pertencentes às gamas 4xx e 5xx são repetidos. No entanto, as solicitações que resultam em um código de status 404 (Não encontrado) não precisam ser repetidas.
  • Alguns serviços de deteção de bots podem responder com um código de estado 200 (OK), mas o conteúdo da resposta pode ser diferente. Esta situação não é tratada na implementação atual. A implementação da validação do comprimento do conteúdo poderia resolver esse problema.

Aqui está o código corrigido, juntamente com comentários que abordam os inconvenientes:

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    Envia um pedido HTTP GET para o URL especificado com um mecanismo avançado de repetição.

    Parameters:
        url (str): O URL para o qual enviar o pedido.
        max_retries (int): O número máximo de vezes para tentar novamente o pedido. A predefinição é 3.
        delay (int): O atraso (em segundos) entre novas tentativas. A predefinição é 1.
        min_content_length (int): O comprimento mínimo do conteúdo da resposta a considerar válido. A predefinição é 10.

    Raises:
        requests.RequestException: Levanta a última exceção se todas as tentativas falharem.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Levantar uma exceção para códigos de estado 4xx ou 5xx
            response.raise_for_status()
            
            # Verificar se o código de estado da resposta é 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Exit loop for 404 errors
            
            # Verificar se o comprimento do texto da resposta é inferior ao comprimento mínimo especificado para o conteúdo
            if len(response.text) < min_content_length:
                print("Response text length is less than specified minimum. Retrying...")
                time.sleep(delay)
                continue  # Repetir o pedido
            
            print('Response Status Code:', response.status_code)
            # Se as condições forem cumpridas, sair do 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.")
                # Aumentar novamente a última exceção se o número máximo de tentativas for atingido
                raise

# Exemplo de utilização
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Tratamento de erros HTTP específicos com proxies

Para determinados erros como 429 Too Many Requests, a utilização de proxies rotativos pode ajudar a distribuir os seus pedidos e evitar a limitação da taxa.

O código abaixo implementa uma estratégia de repetição avançada juntamente com o uso de proxies. Desta forma, podemos implementar um mecanismo de repetição de pedidos Python. A utilização de proxies de raspagem da Web de alta qualidade também é importante. Estes proxies devem ter um bom algoritmo de rotação de proxies e um conjunto fiável.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    Envia um pedido HTTP GET para o URL especificado com um mecanismo avançado de repetição.

    Parameters:
        url (str): O URL para o qual enviar o pedido.
        max_retries (int): O número máximo de tentativas para repetir o pedido. A predefinição é 3.
        delay (int): O atraso (em segundos) entre novas tentativas. A predefinição é 1.
   
    Raises:
        requests.RequestException: Levanta a última exceção se todas as tentativas falharem.
    """
    
    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)
            # Levantar uma exceção para códigos de estado 4xx ou 5xx
            response.raise_for_status()
            
            # Verificar se o código de estado da resposta é 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Sair do ciclo para erros 404
            
            # Verificar se o comprimento do texto de resposta é inferior a 10 caracteres
            if len(response.text) < min_content_length:
                print("Response text length is less than 10 characters. Retrying...")
                time.sleep(delay)
                continue  # Repetir o pedido
            
            print('Response Status Code:', response.status_code)
            # Se as condições forem cumpridas, sair do 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.")
                # Aumentar novamente a última exceção se o número máximo de tentativas for atingido
                raise

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

As novas tentativas de pedidos em Python são cruciais para uma recolha eficaz de dados da Web. Os métodos que discutimos para gerir as novas tentativas podem ajudar a evitar o bloqueio e melhorar a eficiência e a fiabilidade da recolha de dados. A implementação dessas técnicas tornará seus scripts de raspagem da Web mais robustos e menos suscetíveis à deteção por sistemas de proteção de bots.

Comentários:

0 Comentários