Найти ошибки в коде асинхронных HTTP-запросов через aiohttp и asyncio.gather

26. Найти ошибки в коде асинхронных HTTP-запросов через aiohttp и asyncio.gather

Условие задачи Найти ошибки в коде ниже

import aiohttp
import asyncio

url = 'https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&titles={title}'
Titles = ['Python', 'Dog', 'Cat']

async def make_req(session, title: str) -> dict:
    async with session.get(url.format(title)) as response:
        return await response.json()

async def main(titles: list = None):
    async with aiohttp.ClientSession() as session:
        tasks = [make_req(session, title) for title in Titles]
        responses = asyncio.gather(*tasks)
        for response in responses:
            print(response)

await main(titles=Titles)
Спойлеры к решению
Подсказки
  • В строке url.format(title) используется неправильная подстановка значения.
  • В шаблоне URL стоит именованный плейсхолдер {title}, значит нужно использовать url.format(title=title).
  • В main() передаётся параметр titles, но внутри используется глобальная переменная Titles.
  • asyncio.gather(*tasks) нужно обязательно ожидать через await.
  • В обычном .py файле нельзя писать await main(...) на верхнем уровне.
  • Для запуска асинхронной функции в обычном скрипте нужно использовать asyncio.run(...).
  • Лучше не собирать URL строкой, а передавать query-параметры через params.
Решение

Ошибки в исходном коде:

async with session.get(url.format(title)) as response:

Здесь ошибка, потому что в строке URL используется именованный плейсхолдер:

{title}

Значит нужно писать так:

url.format(title=title)

Дальше:

tasks = [make_req(session, title) for title in Titles]

Функция main() принимает аргумент titles, но внутри используется глобальная переменная Titles. Правильно использовать переданный аргумент:

tasks = [make_req(session, title) for title in titles]

Дальше:

responses = asyncio.gather(*tasks)

asyncio.gather() возвращает awaitable-объект. Его нужно ожидать:

responses = await asyncio.gather(*tasks)

Исправленный вариант:

import aiohttp
import asyncio


URL = "https://en.wikipedia.org/w/api.php"


async def make_req(session: aiohttp.ClientSession, title: str) -> dict:
    params = {
        "action": "query",
        "prop": "extracts",
        "exintro": True,
        "explaintext": True,
        "titles": title,
        "format": "json",
    }

    async with session.get(URL, params=params) as response:
        response.raise_for_status()
        return await response.json()


async def main(titles: list[str]) -> None:
    async with aiohttp.ClientSession() as session:
        tasks = [
            make_req(session, title)
            for title in titles
        ]

        responses = await asyncio.gather(*tasks)

        for response in responses:
            print(response)


titles = ["Python", "Dog", "Cat"]

asyncio.run(main(titles))

Что было исправлено:

1. URL теперь собирается через params, а не через ручной format.
2. В запрос добавлен format=json, чтобы API вернул JSON.
3. main() использует переданный аргумент titles, а не глобальную переменную Titles.
4. asyncio.gather() теперь ожидается через await.
5. Для запуска используется asyncio.run(main(titles)).
6. Добавлен response.raise_for_status(), чтобы HTTP-ошибки не игнорировались.

Более близкий к исходному коду вариант:

import aiohttp
import asyncio


url = "https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&format=json&titles={title}"
titles = ["Python", "Dog", "Cat"]


async def make_req(session, title: str) -> dict:
    async with session.get(url.format(title=title)) as response:
        response.raise_for_status()
        return await response.json()


async def main(titles: list[str]) -> None:
    async with aiohttp.ClientSession() as session:
        tasks = [
            make_req(session, title)
            for title in titles
        ]

        responses = await asyncio.gather(*tasks)

        for response in responses:
            print(response)


asyncio.run(main(titles))

Главная причина ошибки: корутины были собраны через asyncio.gather(), но результат gather() не был ожидаем через await. Плюс в URL была неправильная подстановка значения в {title}.