Как сделать параллельные асинхронные c HTTP-запросы, используя httpx (против aiohttp) в Python? - PullRequest
0 голосов
/ 01 мая 2020

Это было основано на опечатке и простой ошибке.

Не удаляется, поскольку в нем есть пример кода для httpx.

Я пытаюсь использовать asyncio для распараллеливания нескольких длинных я sh запускаю веб-запросы. Поскольку я выполняю миграцию из библиотеки requests, я хотел бы использовать библиотеку httpx из-за аналогичного API. Моя среда - это Python 3.7.7 дистрибутива Anaconda со всеми установленными необходимыми пакетами (Windows 10).

Однако, несмотря на возможность использовать httpx для синхронных веб-запросов (или для последовательного выполнения асинхронных операций) c запросов, которые выполняются один за другим), мне не удалось выполнить более одного асинхронного c запроса одновременно, несмотря на то, что это легко сделать с помощью библиотеки aiohttp.

Вот пример кода, который работает чисто в aiohttp: (Обратите внимание, что я работаю в Jupyter, поэтому у меня уже есть событие l oop, таким образом, отсутствие asyncio.run().

import aiohttp
import asyncio
import time
import httpx

async def call_url(session):
    url = "https://services.cancerimagingarchive.net/services/v3/TCIA/query/getCollectionValues"        
    response = await session.request(method='GET', url=url)
    #response.raise_for_status() 
    return response

for i in range(1,5):
    start = time.time() # start time for timing event
    async with aiohttp.ClientSession() as session: #use aiohttp
    #async with httpx.AsyncClient as session:  #use httpx
        await asyncio.gather(*[call_url(session) for x in range(i)])
    print(f'{i} call(s) in {time.time() - start} seconds')

Это приводит к в ожидаемом профиле времени ответа:

1 call(s) in 7.9129478931427 seconds
2 call(s) in 8.876991510391235 seconds
3 call(s) in 9.730034589767456 seconds
4 call(s) in 10.630006313323975 seconds

Однако, если я раскомментирую async with httpx.AsyncClient as session: #use httpx и закомментирую async with aiohttp.ClientSession() as session: #use aiohttp (чтобы заменить httpx на aiohttp), я получу следующую ошибку:

AttributeError                            Traceback (most recent call last)
<ipython-input-108-25244245165a> in async-def-wrapper()
     17         await asyncio.gather(*[call_url(session) for x in range(i)])
     18     print(f'{i} call(s) in {time.time() - start} seconds')

AttributeError: __aexit__

В своем онлайн-исследовании я смог найти только одну статью среднего уровня Саймона Хэйва, показывающую, как использовать httpx для параллельного запроса. См. https://medium.com/swlh/how-to-boost-your-python-apps-using-httpx-and-asynchronous-calls-9cfe6f63d6ad

Однако, пример asyn c кода даже не использует asyn c объект сеанса, поэтому я немного подозрительно начинал. Код не выполняется ни в среде Python 3.7.7, ни в Jupyter. (Код здесь: https://gist.githubusercontent.com/Shawe82/a218066975f4b325e026337806f8c781/raw/3cb492e971c13e76a07d1a1e77b48de94aa7229c/concurrent_download.py)

Это приводит к этой ошибке:

Traceback (most recent call last):
  File ".\async_http_test.py", line 24, in <module>
    asyncio.run(download_all_photos('100_photos'))
  File "C:\Users\stborg\AppData\Local\Continuum\anaconda3\envs\fastai2\lib\asyncio\runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "C:\Users\stborg\AppData\Local\Continuum\anaconda3\envs\fastai2\lib\asyncio\base_events.py", line 587, in run_until_complete
    return future.result()
  File ".\async_http_test.py", line 16, in download_all_photos
    resp = await httpx.get("https://jsonplaceholder.typicode.com/photos")
TypeError: object Response can't be used in 'await' expression

Я явно что-то не так делаю, так как httpx построен для асин c. Я просто не уверен, что это такое!

Ответы [ 2 ]

0 голосов
/ 01 мая 2020

OK. Это откровенно смущает. Нет необходимости в обходном пути. В заявлении о проблеме я полностью пренебрег вызовом конструктора AsyncClient ... Я не могу поверить, что пропустил это так долго. О, мой ...

Чтобы исправить, просто добавьте отсутствующую скобку в конструктор AsyncClient:

    async with httpx.AsyncClient() as session:  #use httpx
        await asyncio.gather(*[call_url(session) for x in range(i)])
0 голосов
/ 01 мая 2020

Экспериментируя далее при написании этого вопроса, я обнаружил тонкую разницу в способах httpx и aiohttp обработки контекстных менеджеров.

В коде, который вводит вопрос, следующий код работал с aiohttp:

    async with aiohttp.ClientSession() as session: #use aiohttp
        await asyncio.gather(*[call_url(session) for x in range(i)])

Этот код передает контекст ClientSession в качестве параметра для метода call_url. Я предполагаю, что после завершения asyncio.gather() ресурсы очищаются в соответствии с обычным оператором with.

Однако, тот же подход с httpx завершается неудачно, как описано выше. Однако это легко исправить, просто полностью исключив оператор with и вручную закрыв AsyncClient.

Другими словами, замените

    async with httpx.AsyncClient as session:  #use httpx
        await asyncio.gather(*[call_url(session) for x in range(i)])

на

.
    session = httpx.AsyncClient() #use httpx
    await asyncio.gather(*[call_url(session) for x in range(i)])
    await session.aclose()

для устранения проблемы.

Вот рабочий код в полном объеме:

import aiohttp
import asyncio
import time
import httpx

async def call_url(session):
    url = "https://services.cancerimagingarchive.net/services/v3/TCIA/query/getCollectionValues"
    response = await session.request(method='GET', url=url)
    return response

for i in range(1,5):
    start = time.time() # start time for timing event
    #async with aiohttp.ClientSession() as session: #use aiohttp
    session = httpx.AsyncClient() #use httpx
    await asyncio.gather(*[call_url(session) for x in range(i)])
    await session.aclose()
    print(f'{i} call(s) in {time.time() - start} seconds')
...