Как выполняется парсинг данных при работе с aiohttp
aiohttpасинхронностьвеб-запросыобработка данныхпарсинг
Что проверяют
Понимаете ли вы, где в асинхронном парсере работает сеть, а где процессор, и не блокируете ли цикл событий. Следом почти всегда спрашивают, как ограничить число одновременных запросов и что делать с таймаутами.
Как отвечать
Разделите задачу на две части: получить ответ и разобрать его.
- сессия: одна
aiohttp.ClientSessionна приложение или весь прогон. Она держит пул соединений, а новая сессия на каждый запрос заново открывает TCP и TLS; - запрос:
async with session.get(url) as resp:, затемresp.raise_for_status(), чтобы 4xx и 5xx не прошли как успех; - тело:
await resp.json()для API,await resp.text()для HTML,await resp.read()для байтов.json()проверяетContent-Typeи бросаетContentTypeError, если пришёл неapplication/json. Большие ответы читают потоком черезresp.content.iter_chunked(); - разбор HTML: BeautifulSoup или
lxml. Это синхронный код, нагружающий процессор, поэтому крупные страницы отдают вasyncio.to_thread()или в пул процессов, иначе остальные запросы стоят; - параллельность:
asyncio.gatherилиasyncio.TaskGroup(Python 3.11+) плюсasyncio.Semaphoreна число одновременных запросов. УTCPConnectorпо умолчанию лимит 100 соединений, а на один хост ограничения нет; - таймауты и повторы:
aiohttp.ClientTimeoutс явнымtotal, по умолчанию общий таймаут пять минут. Встроенных повторов нет: пишут свой цикл с экспоненциальной задержкой или берутtenacity, а на 429 уважаютRetry-After.
Где спотыкаются
Создают ClientSession внутри функции запроса. На сотне URL это сотня пулов и лишние рукопожатия, а на тысячах можно упереться в лимит открытых файлов.
Парсят тяжёлый HTML прямо в корутине и удивляются, что асинхронный сборщик работает не быстрее обычного requests.
Запускают gather на десятки тысяч задач без семафора. Сайт отвечает 429 или банит по IP, а память уходит на ожидающие корутины.