Aiohttp-retry (повтор неуспешного подключения)



При асинхронном парсинге вы получаете не только высокую скорость сбора данных, но и повышенную вероятность получить бан по IP. Как работать с прокси, мы подробно разобрали в прошлом модуле. Также нередки случаи, когда сервер закрывает одно из ваших асинхронных подключений, это чаще всего происходит из-за большого количества запросов. Это еще не бан, но запрос сервером обработан не был. Для повторного запроса в случае неудачи существует отличный клиент, который работает совместно с aiohttp и называется aiohttp-retry.

Установите этот модуль себе в окружающую среду вашего проекта, он нам понадобится для запуска кода.

Установка:

pip install aiohttp-retry

Импорт:

from aiohttp_retry import RetryClient

Самый простой пример из документации. Но у такого примера есть один недостаток: по умолчанию будет производиться всего один повторный запрос. В случае, если и второй запрос окажется неудачным , вы не получите требуемые данные. Мне пришлось немного модифицировать пример из документации, мы ведь любим пользоваться менеджером контекста with/as. В этом случае нам не нужно закрывать вручную нашу сессию командой => await client_session.close()

import aiohttp
from aiohttp_retry import RetryClient

async def main():
    async with aiohttp.ClientSession() as client_session:
        retry_client = RetryClient(client_session=client_session)
        async with retry_client.get('https ://ya.ru') as response:
            print(response.status)

Для того, чтобы у нас была возможность управлять количеством повторных подключений, нам потребуется использовать класс ExponentialRetry из модуля aiohttp_retry. Импортируем этот класс из модуля и немного модифицируем первый пример.

Пример, демонстрирующий использование RetryClient()  для асинхронного опроса нескольких html страниц.

import time
import asyncio
import aiohttp
from aiohttp_retry import RetryClient, ExponentialRetry

# Последние 2 ссылки — 404-е, добавлены в демонстрационных целях
links = ['https ://mob25.com/watch/1/1_1.html',
         'https ://mob25.com/watch/1/1_2.html',
         'https ://mob25.com/watch/1/1_3.html',
         'https ://mob25.com/watch/8/1_3.html',
         'https ://mob25.com/watch/8/2_3.html']

# Корутина для вывода сообщения вида link:response.status
async def get_data(retry_client, link):
    async with retry_client.get(link) as response:
        print(f'{link}:{response.status}')

# Базовая корутина
async def main():
    async with aiohttp.ClientSession() as client_session:

        # statuses=[404] выбран для демонстрации, на практике
        # повторное обращение к несуществующей странице скорее всего бессмысленно
        retry_options = ExponentialRetry(attempts=4, statuses={404})
        async with RetryClient(
                raise_for_status=False, retry_options=retry_options,
                client_session=client_session) as retry_client:
            await asyncio.gather(*[get_data(retry_client, link) for link in links])

if __name__ == '__main__':

    start = time.time()
    asyncio.run(main())
    print(f'время: {time.time() - start}')

Если изменять количество попыток, то можно увидеть закономерность изменения времени, затраченного на работу скрипта:

При attempts=4 время: 1.6515371799468994

При attempts=5 время: 3.2961649894714355

При attempts=6 время: 6.614838123321533

Что косвенно демонстрирует экспоненциальное нарастание паузы между запросами.

Давайте разбираться, что тут написано и как с этим работать.

В базовой асинхронной функции main() c  помощью контекстного менеджера и библиотеки aiohttp создаем объект клиентской сессии client_session.

retry_options = ExponentialRetry(attempts=4, statuses={404}) — создаем экземпляр класса ExponentialRetry (для изменения части политик повторного запроса принятых по умолчанию) и передаем атрибут attempts=, этот атрибут принимает целое число, которое указывает на количество  повторных попыток подключения. В данном примере будет произведено четыре попытки повторных подключений.

statuses=  — принимает множество (set) статусов на которые нужно вызвать повторный запрос. Последними в списке links нашего примера стоят несуществующие страницы (404), на них и будут продемонстрированы повторные попытки подключения.

Ниже указаны все доступные атрибуты.

ExponentialRetry(attempts=10) — количество повторных подключений (int);

ExponentialRetry(start_timeout=0.5) — базовое время ожидания, с каждым новым запросом значение возрастает экспоненциально (float);

ExponentialRetry(max_timeout=30.0) — максимально возможное время ожидания (float);

ExponentialRetry(factor=3.0) — на переданное значение будет увеличиваться timeout ожидания для следующего запроса (float);

ExponentialRetry(statuses={400,403}) — на каких статусах нужно повторить запрос. set(int, int) ;

ExponentialRetry(exceptions=[Exсeption, TimeoutError]) — при каких исключениях повторять запрос, по умолчанию None. list(type_Exception, type_Exception));

ExponentialRetry(retry_all_server_errors=True) — повторить подключение при любых ошибках сервера, явно указывать не нужно, т.к. по умолчанию True.

С помощью контекстного менеджера создаем объект RetryClient()  — retry_client, а в атрибутах передаём следующие параметры:

RetryClient(client_session=client_session) — передаёт объект сессии которую мы получили от aiohttp;

RetryClient(retry_options=retry_options) — передает экземпляр класса ExponentialRetry(), в котором мы настраивали параметры повторного запроса.

RetryClient(raise_for_status=True) — Если True, использует время ответа сервера в качестве параметра для расчета следующего timeout, лучше использовать параметр False, который установлен по умолчанию. Если установить True,  вы можете столкнуться с проблемой, когда сервер не ответил и следующий timeout получит значение None, тогда расчёт времени для следующего запроса, рассчитан не будет , потому что из None невозможно ничего рассчитать.

Через await asyncio.gather() создаем из get_data() и запускаем асинхронное выполнение ряда задач для тестирования статусов страниц из списка links.

Создаем асинхронную функцию get_data(retry_client, link) для вывода сообщения {link}:{response.status}.

Передаем в нее объект RetryClient() — retry_client и ссылку на проверяемую страницу — link.

Внутри функции выполняется асинхронный GET-запрос к указанному в link URL.

Если запрос не удаётся (например, из-за временной ошибки соединения или серверной ошибки, реакции на заданный статус), RetryClient автоматически попытается повторить запрос согласно заданной политике повторных попыток.

В примере мы специально провоцируем появление в ответе статуса 404 для двух последних страниц.

Запуск скрипта происходит по команде: asyncio.run(main()), что приводит к созданию цикла событий и автоматическому созданию и запуску в нем задачи из базовой корутины main().

​​​​​​​RetryClient(logger=[type_logger])  устанавливает логирование запросов.



Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: