Python · API · Продвинутый уровень

Асинхронные запросы с httpx

5 задач

Используйте httpx с asyncio для параллельных запросов к API и ускорения скриптов с несколькими вызовами.

httpx и asyncio

#
Библиотека `requests` выполняет запросы последовательно -- каждый следующий начинается только после завершения предыдущего: ``` GET /products/1/ → ждём → GET /products/2/ → ждём → ... ``` Если каждый запрос занимает 200 мс, то 10 запросов -- это 2 секунды. Асинхронные запросы выполняются параллельно: ``` GET /products/1/ ┐ GET /products/2/ ├→ все выполняются одновременно → ждём всех GET /products/3/ ┘ ``` Те же 10 запросов завершаются за те же ~200 мс. `httpx` -- это библиотека, похожая на `requests`, с поддержкой асинхронного режима. В синхронном режиме она работает так же, как `requests`: ```python import httpx # Синхронно -- то же самое что requests response = httpx.get('https://apilearn.tukas.dev/api/products/') print(response.json()['count']) ``` Для асинхронного режима используйте `AsyncClient` с `async/await`: ```python import httpx import asyncio async def fetch_products(): async with httpx.AsyncClient() as client: response = await client.get('https://apilearn.tukas.dev/api/products/') return response.json() asyncio.run(fetch_products()) ``` `async with` открывает клиент и автоматически закрывает его по завершении блока. `await` останавливает текущую корутину, пока не придёт ответ, но не блокирует цикл событий -- поэтому другие запросы могут выполняться параллельно. `asyncio.gather()` запускает несколько корутин одновременно и ждёт всех: ```python results = await asyncio.gather( client.get(url1), client.get(url2), client.get(url3), ) # results -- список объектов Response в том же порядке ``` Когда использовать async: - Много независимых запросов (все категории, все страницы, детали продуктов). - Запросы не зависят от результатов друг друга. - Каждый запрос имеет заметную задержку (сетевые вызовы почти всегда имеют). Async не помогает, когда запросы последовательны по природе (например, нужен результат запроса 1, чтобы знать что спрашивать в запросе 2).

Практические примеры asyncio.gather

#
Практические примеры параллельных запросов с httpx: Получить количество продуктов во всех категориях параллельно: ```python import httpx import asyncio BASE_URL = 'https://apilearn.tukas.dev' async def fetch_category_count(client, slug): response = await client.get( f'{BASE_URL}/api/products/', params={'category': slug, 'page_size': 1}, ) return slug, response.json()['count'] async def main(): async with httpx.AsyncClient() as client: r = await client.get(f'{BASE_URL}/api/categories/') categories = r.json()['results'] slugs = [cat['slug'] for cat in categories] tasks = [fetch_category_count(client, slug) for slug in slugs] results = await asyncio.gather(*tasks) for slug, count in results: print(f'{slug}: {count}') asyncio.run(main()) ``` Получить несколько продуктов по id параллельно: ```python import httpx import asyncio BASE_URL = 'https://apilearn.tukas.dev' async def fetch_product(client, product_id): response = await client.get(f'{BASE_URL}/api/products/{product_id}/') return response.json() async def main(): async with httpx.AsyncClient() as client: products = await asyncio.gather(*[ fetch_product(client, i) for i in range(1, 6) ]) for p in products: print(p['name'], p['price']) asyncio.run(main()) ``` `*[...]` распаковывает список в позиционные аргументы для `asyncio.gather`. `gather(*tasks)` эквивалентен `gather(task1, task2, task3, ...)`. Примечание по httpx vs requests: `httpx` поддерживает и синхронный, и асинхронный режимы, имеет почти идентичный API с `requests` и поддерживает HTTP/2. Для сугубо синхронных скриптов `requests` вполне подходит. Используйте `httpx`, когда нужен async.

Справочник httpx и asyncio

#
Сравнение httpx и requests: ``` requests httpx Синхронный режим requests.get() httpx.get() Асинхронный режим — httpx.AsyncClient Сессия requests.Session httpx.Client (синх) / httpx.AsyncClient API запросов .get .post .json() одинаковое raise_for_status ✓ ✓ ``` Параллельные запросы с asyncio.gather: ```python # Список корутин → gather → список результатов (в том же порядке) responses = await asyncio.gather( client.get(url1), client.get(url2), ) # responses[0] соответствует url1, responses[1] — url2 ``` Распаковка списка с `*`: ```python # Вместо: await asyncio.gather(fetch(1), fetch(2), fetch(3), fetch(4), fetch(5)) # Используйте: await asyncio.gather(*[fetch(i) for i in range(1, 6)]) ``` Когда что использовать: ``` requests Простые скрипты, последовательные запросы, небольшое количество запросов httpx async Много независимых запросов, скорость важна ```
01

Первый асинхронный запрос

#

Напишите асинхронную функцию с httpx.AsyncClient, которая получает /api/products/ и выводит количество продуктов. Запустите её через asyncio.run().

import httpx
import asyncio

BASE_URL = 'https://apilearn.tukas.dev'

async def fetch_products():
    # Используйте httpx.AsyncClient чтобы получить /api/products/
    pass

asyncio.run(fetch_products())
Решение
import httpx
import asyncio

BASE_URL = 'https://apilearn.tukas.dev'

async def fetch_products():
    async with httpx.AsyncClient() as client:
        response = await client.get(f'{BASE_URL}/api/products/')
        data = response.json()
        print(data['count'])

asyncio.run(fetch_products())
02

Параллельные запросы с gather

#

Используйте asyncio.gather() чтобы сделать два запроса параллельно: GET /api/products/ и GET /api/categories/. Выведите общее количество продуктов и количество категорий.

import httpx
import asyncio

BASE_URL = 'https://apilearn.tukas.dev'

async def main():
    async with httpx.AsyncClient() as client:
        # Сделайте оба запроса параллельно через gather
        pass

asyncio.run(main())
Решение
import httpx
import asyncio

BASE_URL = 'https://apilearn.tukas.dev'

async def main():
    async with httpx.AsyncClient() as client:
        products_resp, categories_resp = await asyncio.gather(
            client.get(f'{BASE_URL}/api/products/'),
            client.get(f'{BASE_URL}/api/categories/'),
        )
    print(products_resp.json()['count'])
    print(len(categories_resp.json()))

asyncio.run(main())
03

Получить несколько продуктов параллельно

#

Напишите корутину fetch_product(client, product_id), которая получает /api/products/{id}/. Используйте asyncio.gather() чтобы получить продукты с id от 1 до 5 одновременно. Выведите название и цену каждого.

import httpx
import asyncio

BASE_URL = 'https://apilearn.tukas.dev'

async def fetch_product(client, product_id):
    # GET /api/products/{product_id}/
    pass

async def main():
    async with httpx.AsyncClient() as client:
        # Соберите и выполните 5 запросов через gather
        pass

asyncio.run(main())
Решение
import httpx
import asyncio

BASE_URL = 'https://apilearn.tukas.dev'

async def fetch_product(client, product_id):
    response = await client.get(f'{BASE_URL}/api/products/{product_id}/')
    return response.json()

async def main():
    async with httpx.AsyncClient() as client:
        products = await asyncio.gather(*[
            fetch_product(client, i) for i in range(1, 6)
        ])
    for p in products:
        print(p['name'], p['price'])

asyncio.run(main())
04

Авторизованный асинхронный клиент

#

Используйте httpx.AsyncClient с заголовком авторизации (полученным через синхронный requests.post). Сделайте параллельные GET к /api/cart/ и /api/users/profile/. Выведите username и количество товаров в корзине.

import httpx
import asyncio
import requests

BASE_URL = 'https://apilearn.tukas.dev'

# 1. Получите токен через requests (синхронно)
# 2. Используйте AsyncClient с заголовком Authorization
# 3. Параллельно получите профиль и корзину
Решение
import httpx
import asyncio
import requests

BASE_URL = 'https://apilearn.tukas.dev'

token = requests.post(f'{BASE_URL}/api/auth/token/', json={
    'username': 'ВАШ_ЛОГИН', 'password': 'ВАШ_ПАРОЛЬ',
}).json()['token']

async def main():
    headers = {'Authorization': f'Token {token}'}
    async with httpx.AsyncClient(headers=headers) as client:
        profile_resp, cart_resp = await asyncio.gather(
            client.get(f'{BASE_URL}/api/users/profile/'),
            client.get(f'{BASE_URL}/api/cart/'),
        )
    print(profile_resp.json()['username'])
    print(len(cart_resp.json()['items']))

asyncio.run(main())
05

Сравнение последовательного и параллельного выполнения

#

Измерьте время выполнения двух подходов для получения продуктов 1-5: сначала с requests в цикле (последовательно), затем с httpx и asyncio.gather (параллельно). Выведите время для обоих вариантов.

import httpx
import asyncio
import requests
import time

BASE_URL = 'https://apilearn.tukas.dev'

# Подход 1: последовательный с requests
start = time.time()
# ... ваш код ...
print(f'Последовательный: {time.time() - start:.2f}s')

# Подход 2: параллельный с httpx
start = time.time()
# ... ваш код ...
print(f'Параллельный: {time.time() - start:.2f}s')
Решение
import httpx
import asyncio
import requests
import time

BASE_URL = 'https://apilearn.tukas.dev'

# Подход 1: последовательный
start = time.time()
for i in range(1, 6):
    requests.get(f'{BASE_URL}/api/products/{i}/')
print(f'Последовательный: {time.time() - start:.2f}s')

# Подход 2: параллельный
async def fetch(client, i):
    return await client.get(f'{BASE_URL}/api/products/{i}/')

async def parallel():
    async with httpx.AsyncClient() as client:
        await asyncio.gather(*[fetch(client, i) for i in range(1, 6)])

start = time.time()
asyncio.run(parallel())
print(f'Параллельный: {time.time() - start:.2f}s')