Получение нескольких URL-адресов с помощью aiohttp в Python 3.5

Поскольку Python 3.5 представил async with синтаксис, рекомендованный в документах для aiohttp изменился. Теперь, чтобы получить один URL-адрес, они предлагают:

import aiohttp
import asyncio

async def fetch(session, url):
    with aiohttp.Timeout(10):
        async with session.get(url) as response:
            return await response.text()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    with aiohttp.ClientSession(loop=loop) as session:
        html = loop.run_until_complete(
            fetch(session, 'http://python.org'))
        print(html)

Как я могу изменить это, чтобы получить набор URL-адресов вместо одного URL-адреса?

В старых asyncio примерах вы бы создали список задач, таких как

    tasks = [
            fetch(session, 'http://cnn.com'),
            fetch(session, 'http://google.com'),
            fetch(session, 'http://twitter.com')
            ]

Я попытался объединить такой список с описанным выше подходом, но не смог.


person Hans Schindler    schedule 08.03.2016    source источник
comment
Не могли бы вы объяснить, в чем ваш провал?   -  person Andrew Svetlov    schedule 09.03.2016
comment
@AndrewSvetlov Рад слышать тебя. Я имею в виду, что я не мог понять, как это сделать. Когда я определяю список задач, а затем использую results = loop.run_until_complete(tasks), я получаю ошибку времени выполнения. async with — это настолько новая функция, о которой так мало литературы, что для людей, изучающих ее, было бы очень удобно, если бы в документе aiohttp был показан пример захвата более одного URL-адреса. Библиотека выглядит потрясающе, просто нужно немного подержать руку, чтобы начать. Благодарю вас!   -  person Hans Schindler    schedule 09.03.2016


Ответы (1)


Для параллельного выполнения вам понадобится asyncio.Task.

Я преобразовал ваш пример в одновременную выборку данных из нескольких источников:

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        if response.status != 200:
            response.raise_for_status()
        return await response.text()

async def fetch_all(session, urls):
    tasks = []
    for url in urls:
        task = asyncio.create_task(fetch(session, url))
        tasks.append(task)
    results = await asyncio.gather(*tasks)
    return results

async def main():    
    urls = ['http://cnn.com',
            'http://google.com',
            'http://twitter.com']
    async with aiohttp.ClientSession() as session:
        htmls = await fetch_all(session, urls)
        print(htmls)

if __name__ == '__main__':
    asyncio.run(main())
person Andrew Svetlov    schedule 09.03.2016
comment
Бесконечно благодарен! Принимаю ваш ответ, но... 1. все еще есть опечатка с размещением скобок. Я отредактирую, если вы не против. 2. Мне кажется, что нужно напечатать фактический результат, строка print(html) вводит в заблуждение, и вам действительно нужно что-то вроде print('\n'.join(list((str(some_task._result) for some_tuple in html for some_task in some_tuple)))), может быть, это можно добавить к ответу? 3. Это кажется действительно полезным, я бы порекомендовал добавить что-то подобное в readthedocs. Спасибо еще раз! :) - person Hans Schindler; 10.03.2016
comment
Андрей, а где можно поставить тест типа if response.status == 200? Если одного URL не существует, скрипт ломается, и я не понимаю, где проверить ответ в async with session.get(url) as response: return await response.text() - person Hans Schindler; 10.03.2016
comment
Спасибо другому человеку, который оставил комментарий раньше. Я задал новый вопрос, чтобы прояснить это. - person Hans Schindler; 10.03.2016
comment
aiohttp.ClientSession(loop=loop) больше не является допустимым синтаксисом. Я получаю сообщение об ошибке: Use async with instead не могли бы вы обновить ответ, чтобы отразить изменения - person Alex Zamai; 29.08.2018
comment
Обновлено для использования aiohttp 3.x и python 3.7. - person Andrew Svetlov; 30.08.2018
comment
Где определен цикл? - person Kalimantan; 19.10.2018
comment
@Kalimantan main() - это обычная сопрограмма. Создайте из него задачу и запустите в цикле событий, как и с любой другой сопрограммой. - person Petr Javorik; 08.12.2018
comment
Эй, это сохраняет порядок URL-адресов? т. е. является ли htmls[i] ответом на urls[i]? - person Dennis Subachev; 24.12.2018
comment
часть asyncio.create_task не нужна. Задача создается из сопрограммы в 3.7. - person n1_; 26.12.2018
comment
Когда я запустил %timeit для этого примера и сравнил его с запросами, запросы были немного быстрее (?). Является ли этот подход оптимальным? - person vgoklani; 19.01.2020
comment
Вместо условного выражения if для статуса просто выполните session.get(url, raise_for_status=True) - person CodeBiker; 09.06.2020