Как я мог использовать запросы в asyncio?

128

Я хочу выполнять параллельные задачи HTTP-запроса asyncio, но считаю, что python-requestsэто заблокирует цикл обработки событий asyncio. Я нашел aiohttp, но он не смог предоставить службу HTTP-запроса с использованием HTTP-прокси.

Поэтому я хочу знать, есть ли способ выполнять асинхронные HTTP-запросы с помощью asyncio.

листовка
источник
1
Если вы просто отправляете запросы, вы можете использовать его subprocessдля параллельного выполнения кода.
WeaselFox 05
Этот метод кажется не изящным ……
флаер
Теперь есть асинхронный порт запросов. github.com/rdbhost/yieldfromRequests
Rdbhost

Ответы:

182

Чтобы использовать запросы (или любые другие библиотеки блокировки) с asyncio, вы можете использовать BaseEventLoop.run_in_executor для запуска функции в другом потоке и выхода из нее для получения результата. Например:

import asyncio
import requests

@asyncio.coroutine
def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = yield from future1
    response2 = yield from future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

При этом оба ответа будут получены параллельно.

В python 3.5 вы можете использовать новый await/ asyncсинтаксис:

import asyncio
import requests

async def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = await future1
    response2 = await future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Подробнее см. PEP0492 .

Кристиан
источник
5
Вы можете объяснить, как именно это работает? Я не понимаю, как это не блокирует.
Скотт Коутс,
32
@christian, но если он работает одновременно в другом потоке, разве это не побеждает суть asyncio?
Скотт Коутс
21
@scoarescoare Вот где появляется часть «если вы делаете это правильно» - метод, который вы запускаете в исполнителе, должен быть самодостаточным ((в основном) как request.get в приведенном выше примере). Таким образом, вам не придется иметь дело с общей памятью, блокировками и т. Д., А сложные части вашей программы по-прежнему будут однопоточными благодаря asyncio.
Кристиан
5
@scoarescoare Основной вариант использования - интеграция с библиотеками ввода-вывода, которые не поддерживают asyncio. Например, я работаю с действительно древним интерфейсом SOAP и использую библиотеку suds-jurko как «наименее плохое» решение. Я пытаюсь интегрировать его с сервером asyncio, поэтому я использую run_in_executor, чтобы сделать вызовы блокировки пены таким образом, чтобы это выглядело асинхронно.
Lucretiel
10
Действительно здорово, что это работает и так легко для устаревшего материала, но следует подчеркнуть, что он использует пул потоков ОС и поэтому не масштабируется как настоящая asyncio-ориентированная библиотека, как это делает
aiohttp
78

aiohttp уже можно использовать с HTTP-прокси:

import asyncio
import aiohttp


@asyncio.coroutine
def do_request():
    proxy_url = 'http://localhost:8118'  # your proxy address
    response = yield from aiohttp.request(
        'GET', 'http://google.com',
        proxy=proxy_url,
    )
    return response

loop = asyncio.get_event_loop()
loop.run_until_complete(do_request())
MindMaster
источник
Что здесь делает разъем?
Маркус Месканен
Он обеспечивает соединение через прокси-сервер
mindmaster
16
Это гораздо лучшее решение, чем использование запросов в отдельном потоке. Поскольку он действительно асинхронный, у него меньше накладных расходов и меньше использования памяти.
Том
14
для python> = 3.5 замените @ asyncio.coroutine на «async» и «yield from» на «await»
Джеймс
41

В приведенных выше ответах все еще используются старые сопрограммы в стиле Python 3.4. Вот что вы бы написали, если бы у вас был Python 3.5+.

aiohttp теперь поддерживает http прокси

import aiohttp
import asyncio

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

async def main():
    urls = [
            'http://python.org',
            'https://google.com',
            'http://yifei.me'
        ]
    tasks = []
    async with aiohttp.ClientSession() as session:
        for url in urls:
            tasks.append(fetch(session, url))
        htmls = await asyncio.gather(*tasks)
        for html in htmls:
            print(html[:100])

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
ospider
источник
1
не могли бы вы уточнить больше URL-адресов? Когда речь идет о параллельном http-запросе, не имеет смысла иметь только один URL.
анонимный,
Легенда. Спасибо!
Adam
@ospider Как можно изменить этот код, чтобы доставлять, скажем, 10 тыс. URL-адресов, используя 100 запросов параллельно? Идея состоит в том, чтобы использовать все 100 слотов одновременно, а не ждать, пока 100 будут доставлены, чтобы начать следующие 100.
Антоан Милков
@AntoanMilkov Это другой вопрос, на который нельзя ответить в области комментариев.
ospider
@ospider Вы правы, вот в чем вопрос: stackoverflow.com/questions/56523043/…
Antoan Milkov
11

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

Если у меня будет какое-то время, я действительно могу это изучить, но я не могу ничего обещать.

Лукаса
источник
Ссылка ведет на
ошибку
8

Хороший случай использования циклов async / await и потоковой передачи приведен в статье Пимина Константина Кефалукоса Простые параллельные HTTP-запросы с Python и asyncio :

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

# Example 3: asynchronous requests with larger thread pool
import asyncio
import concurrent.futures
import requests

async def main():

    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:

        loop = asyncio.get_event_loop()
        futures = [
            loop.run_in_executor(
                executor, 
                requests.get, 
                'http://example.org/'
            )
            for i in range(20)
        ]
        for response in await asyncio.gather(*futures):
            pass


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Илья Русин
источник
2
проблема в том, что если мне нужно выполнить 10000 запросов с кусками по 20 исполнителей, мне нужно дождаться завершения всех 20 исполнителей, чтобы начать со следующих 20, верно? Я не могу это сделать, for i in range(10000)потому что один запрос может завершиться ошибкой или тайм-аутом, верно?
Sanandrea
1
Не могли бы вы объяснить, зачем вам asyncio, если вы можете сделать то же самое, просто используя ThreadPoolExecutor?
Асаф Пинхасси
@lya Rusin На основании чего выставляем количество max_workers? Это связано с количеством процессоров и потоков?
alt-f4