Извлеките несколько URL-адресов с помощью asyncio / aiohttp и повторите попытку в случае сбоя - PullRequest
1 голос
/ 01 июля 2019

Я пытаюсь написать несколько асинхронных GET-запросов с пакетом aiohttp, и выяснил большинство частей, но мне интересно, каков стандартный подход при обработке сбоев (возвращаемых как исключения).

Общая идея моего кода до сих пор (после некоторых проб и ошибок я следую подходу здесь ):

import asyncio
import aiofiles
import aiohttp
from pathlib import Path

with open('urls.txt', 'r') as f:
    urls = [s.rstrip() for s in f.readlines()]

async def fetch(session, url):
    async with session.get(url) as response:
        if response.status != 200:
            response.raise_for_status()
        data = await response.text()
    # (Omitted: some more URL processing goes on here)
    out_path = Path(f'out/')
    if not out_path.is_dir():
        out_path.mkdir()
    fname = url.split("/")[-1]
    async with aiofiles.open(out_path / f'{fname}.html', 'w+') as f:
        await f.write(data)

async def fetch_all(urls, loop):
    async with aiohttp.ClientSession(loop=loop) as session:
        results = await asyncio.gather(*[fetch(session, url) for url in urls],
                return_exceptions=True)
        return results

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(fetch_all(urls, loop))

Теперь все работает нормально:

  • Как и ожидалось, переменная results заполняется записями None, где соответствующий URL [т.е. с тем же индексом в переменной массива urls, т.е. с тем же номером строки во входном файле urls.txt] былуспешно запрошен, и соответствующий файл записан на диск.
  • Это означает, что я могу использовать переменную результатов, чтобы определить, какие URL не были успешными (эти записи в results не равны None)

Я ознакомился с несколькими руководствами по использованию различных асинхронных пакетов Python (aiohttp, aiofiles и asyncio), но я не видел стандартного способаобработайте этот последний шаг.

  • Должна ли повторная попытка отправить запрос GET после того, как оператор await завершил / завершил?
  • ... или долженповторная попытка отправить запрос GET инициируется каким-либо обратным вызовом при сбое
    • Ошибки выглядят следующим образом: (ClientConnectorError(111, "Connect call failed ('000.XXX.XXX.XXX', 443)") то есть запрос на IP-адрес 000.XXX.XXX.XXX в порту 443 завершился неудачей, вероятно, потомунекоторый предел от сервера, который я должен соблюдать, ожидая с тайм-аутом перед повторной попыткой.
  • Есть ли какой-то тип ограничения, который я мог бы рассмотреть, чтобы установить количество запросов, а непробуете их все?
  • Я получаю около 40-60 успешных запросов при попытке нескольких сотен (более 500) URL-адресов в моем списке.

Наивно я ожидал run_until_complete, чтобы обработать это таким образом, чтобы он завершил работу при запросе всех URL, но это не так.

Я не работал с асинхронным Python и сессиями / цикламидо, поэтому был бы признателен за любую помощь, чтобы найти, как получить results.Пожалуйста, дайте мне знать, если я могу дать больше информации, чтобы улучшить этот вопрос, спасибо!

1 Ответ

1 голос
/ 01 июля 2019

Должна ли повторная попытка отправить запрос GET после того, как оператор await завершил / завершил?... или если попытка отправить запрос GET будет инициирована каким-либо обратным вызовом при сбое

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

async def fetch(session, url):
    data = None
    while data is None:
        try:
            async with session.get(url) as response:
                response.raise_for_status()
                data = await response.text()
        except aiohttp.ClientError:
            # sleep a little and try again
            await asyncio.sleep(1)
    # (Omitted: some more URL processing goes on here)
    out_path = Path(f'out/')
    if not out_path.is_dir():
        out_path.mkdir()
    fname = url.split("/")[-1]
    async with aiofiles.open(out_path / f'{fname}.html', 'w+') as f:
        await f.write(data)

Наивно, я ожидал, что run_until_complete справится с этим таким образом, что он завершит работу при запросе всех URL

.Термин «завершенный» в техническом смысле означает сопрограмму , завершившую (продолжающую курс), что достигается либо возвратом сопрограммы, либо возникновением исключения.

...