Реализация повторных запросов в Python

Комментарии: 0

Многие разработчики предпочитают использовать библиотеку Requests в Python для веб-скрапинга, благодаря ее простоте и эффективности. Однако, несмотря на все ее преимущества, библиотека Requests имеет ограничения, особенно когда речь идет о неудачных запросах, которые могут серьезно повлиять на стабильность процесса извлечения данных. В этой статье мы рассмотрим, как реализовать повторные запросы в Python, что позволит более эффективно обрабатывать HTTP-ошибки и значительно повысить надежность выполняемых скриптов веб-скрапинга.

Начало работы с библиотекой Requests

Перед началом работы убедитесь, что у вас установлен Python и выбранная среда разработки. После этого установите библиотеку Requests, если она еще не установлена.

pip install requests

Далее, попробуем отправить запрос к сайту example.com, используя модуль Requests в Python. Ниже представлена простая функция, которая выполняет это действие:

import requests

def send_request(url):
    """
   Отправляет HTTP GET запрос на указанный URL и выводит код состояния ответа.
    
    Parameters:
        url (str): URL для отправки запроса.
    """
    response = requests.get(url)
    print('Response Status Code: ', response.status_code)

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

Результат выполнения кода показан ниже:

How to implement request retries in Python.png

Рассмотрим подробнее, что представляют собой HTTP-статус коды.

Определение кодов состояния HTTP

Серверы используют HTTP-статус коды для информирования о результате обработки запросов. Вот краткий обзор различных кодов состояния:

  1. 1xx (Информационные): запрос был получен и продолжает обрабатываться.
  2. 2xx (Успех): запрос был успешно обработан:
    • 200 OK: запрос успешно выполнен. Этот код является подтверждением успешной обработки запроса.
  3. 3xx (Перенаправление): для завершения запроса необходимо выполнить дополнительные действия.
  4. 4xx (Ошибка клиента): запрос содержит ошибку, обычно из-за действий на стороне клиента.
  5. 5xx (Ошибка сервера): сервер столкнулся с проблемой, которая не позволила выполнить корректный запрос.
    • 500 Internal Server Error: ошибка сервера, который не смог обработать запрос по причинам внутренних неполадок.
    • 504 Gateway Timeout: сервер не получил своевременный ответ и не смог завершить обработку запроса.

В нашем примере статус код 200 свидетельствует о том, что запрос к https://example.com был успешно обработан.

Статус коды могут также помочь в обнаружении активности ботов и указывать на ограничения доступа, вызванные схожей с действиями ботов активностью.

Ниже представлен краткий обзор таких кодов:

  1. 429 too many requests: этот код сообщает, что было отправлено слишком много запросов в короткий промежуток времени. Это типичный ответ сервера, когда активность ботов превышает установленные лимиты запросов.
  2. 403 forbidden: сервер отказывается обслуживать запрос, возможно, потому что обнаружил, что он исходит от бота. Такое может произойти на основании анализа User-Agent или других признаков автоматической активности.
  3. 401 unauthorized: этот статус указывает на необходимость аутентификации, которой, возможно, не обладает бот.
  4. 503 Service Unavailable: сервер временно не в состоянии обработать запрос из-за перегрузки, связанной с активностью автоматизированных запросов на систему.

Реализация механизма повторных попыток в Python

При работе с сетевыми запросами неизбежны случаи, когда запросы не удаются из-за временных сетевых сбоев или перегрузки сервера. Для повышения стабильности скрапинга, можно реализовать механизм повторных попыток.

Базовый механизм повторных попыток

Функция “send_request_with_basic_retry_mechanism” выполняет HTTP GET-запросы по заданному URL с базовым механизмом повторных попыток, который срабатывает только в случае возникновения исключений, связанных с сетью или запросом, таких как ошибка подключения. Она повторяет запрос количество раз, определяемое параметром “max_retries”. Если все попытки завершаются неудачей с таким исключением, функция генерирует последнее встреченное исключение.

import requests
import time

def send_request_with_basic_retry_mechanism(url, max_retries=2):
    """
     Отправляет HTTP GET-запрос на URL с базовым механизмом повторных попыток.
    
    Parameters:
        url (str): URL для отправки запроса.

        max_retries (int): Максимальное количество попыток повторного запроса.


    Raises:
        requests.RequestException: Генерирует последнее исключение, если все попытки неудачны.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            print('Response status: ', response.status_code)
            break # Выйти из цикла, если запрос выполнен успешно
        except requests.RequestException as error:
            print(f"Attempt {attempt+1} failed:", error)
            if attempt < max_retries - 1:
                print(f"Retrying...")
                time.sleep(delay)  # Подождите перед повторной попыткой
            else:
                print("Max retries exceeded.")
                # Сгенерируйте последнее исключение, если достигнуто максимальное количество попыток
                raise
                send_request_with_basic_retry_mechanism('https://example.com')

Продвинутый механизм повторных попыток

Для более надёжного скрапинга данных с веб-сайтов, использующих механизмы обнаружения ботов, необходимо адаптировать наш подход к реализации повторных попыток. В случаях, когда запросы могут быть заблокированы из-за подозрений в бот-активности или из-за временных проблем с сетью или сервером, полезно иметь механизм, который позволяет адаптивно управлять повторными попытками.

Функция “send_request_with_advance_retry_mechanism” реализует такой подход, отправляя HTTP GET-запросы на заданный URL с возможностью настройки количества повторных попыток и задержки между ними. Эта функция не только повторяет запросы при возникновении ошибок, но и учитывает необходимость снижения частоты запросов, чтобы избежать обнаружения анти-ботовыми системами безопасности.

Параметр задержки играет ключевую роль в механизме повторных попыток, поскольку он предотвращает чрезмерную нагрузку на сервер путем отправки множественных запросов в короткие временные промежутки. Вместо этого задержка между попытками дает серверу достаточно времени для обработки запросов, создавая впечатление, что за запросами стоит человек, а не автоматизированная система.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1):
    """
    Отправляет HTTP GET-запрос на указанный URL с продвинутым механизмом повторных попыток.
    
    Parameters:
        url (str): URL, на который отправляется запрос.
        max_retries (int): Максимальное количество повторных попыток запроса. По умолчанию - 3.
        delay (int): Задержка (в секундах) между попытками. По умолчанию - 1.

    Raises:
        requests.RequestException: Генерирует последнее исключение, если все попытки неудачны.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Сгенерировать исключение для статус-кодов 4xx или 5xx
            response.raise_for_status()
            print('Response Status Code:', response.status_code)
        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.")
                raise

# Пример использования
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Недостатки данной реализации:

  • Повторные попытки выполняются для всех статус-кодов в диапазонах 4xx и 5xx. Однако запросы, приводящие к статусу 404 (Не найдено), не нуждаются в повторении.
  • Некоторые сервисы обнаружения ботов могут отвечать статус-кодом 200 (ОК), но содержание ответа может отличаться. В текущей реализации это не учитывается, для решения проблемы необходимо дополнительно проверять длину содержимого.

Ниже представлен корректный код, устраняющий эти недостатки:

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
     Отправляет HTTP GET-запрос на указанный URL с продвинутым механизмом повторных попыток.

    Parameters:
        url (str): URL, на который отправляется запрос.
        max_retries (int): Максимальное количество раз, которое запрос может быть повторен. По умолчанию 3.
        delay (int): Задержка (в секундах) между попытками. По умолчанию 1.
        min_content_length (int): Минимальная длина содержимого ответа, чтобы считать его действительным. По умолчанию 10.

    Raises:
        requests.RequestException: Генерирует последнее исключение, если все попытки неудачны.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
           # Сгенерировать исключение для статус-кодов 4xx или 5xx
            response.raise_for_status()
            
           # Проверить, равен ли статус-код ответа 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Выход из цикла при ошибках 404
            
           # Проверить, меньше ли длина текста ответа указанной минимальной длины содержимого
            if len(response.text) < min_content_length:
                print("Response text length is less than specified minimum. Retrying...")
                time.sleep(delay)
                continue  # Повторить запрос
            
            print('Response Status Code:', response.status_code)
            # Если условия выполнены, выйти из цикла
            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.")
               # Сгенерировать последнее исключение, если достигнуто максимальное количество попыток
                raise

# Пример использования
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Обработка HTTP ошибок с использованием прокси

В случае с такими ошибками 429 too many requests, использование прокси с ротацией может помочь распределить ваши запросы и избежать ограничений по частоте запросов.

Ниже приведен код, реализующий продвинутую стратегию повторных попыток вместе с использованием прокси. Важно использовать резидентские или мобильные прокси для веб-скрапинга, поскольку они позволяют гибко настраивать алгоритм ротации и предоставляют обширный пул адресов.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
  Отправляет HTTP GET-запрос на указанный URL с продвинутым механизмом повторных попыток.

    Parameters:
        url (str): URL, на который отправляется запрос.
        max_retries (int): Максимальное количество раз, которое запрос может быть повторен. По умолчанию 3.
        delay (int): Задержка (в секундах) между попытками. По умолчанию 1.
   
    Raises:
        requests.RequestException: Генерирует последнее исключение, если все попытки неудачны.
    """
    
    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)
            # Сгенерировать исключение для статус-кодов 4xx или 5xx
            response.raise_for_status()
            
            # Проверить, равен ли статус-код ответа 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Выход из цикла при ошибках 404
            
            # Проверить, меньше ли 10 символов длина текста ответа
            if len(response.text) < min_content_length:
                print("Response text length is less than 10 characters. Retrying...")
                time.sleep(delay)
                continue # Повторить запрос
            
            print('Response Status Code:', response.status_code)
            # Если условия выполнены, выйти из цикла
            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.")
                # Сгенерировать последнее исключение, если достигнуто максимальное количество попыток
                raise

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

Повторные попытки запросов в Python — ключевой элемент успешного веб-скрапинга. Рассмотренные способы управления повторными попыткамипомогают избежать блокировок и обеспечивают более эффективный и надежный сбор данных. Применение этих техник делает ваши скрипты веб-скрапинга более эффективными и устойчивыми перед системами обнаружения ботов.

Комментарии:

0 комментариев