Реалізація повторних запитів у 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 Коментаріїв