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}.