Но как только [BeautifulSoup] не закомментирован, создается впечатление, что aiohttp просто отправляет GET-запросы один за другим, ожидая завершения каждого запроса.Любая идея почему?
Потому что asyncio является однопоточным.Если анализ BeautifulSoup требует много ресурсов ЦП, это время нельзя использовать для обслуживания HTTP-запросов и ответов, поэтому ваши загрузки будут эффективно сериализованы или, по крайней мере, строго ограничены.К счастью, есть простой способ исправить это, просто замените:
soup = BeautifulSoup(res, 'lxml')
на:
loop = asyncio.get_event_loop()
soup = loop.run_in_executor(None, BeautifulSoup, res, 'lxml')
Это будет указывать asyncio отправлять анализатор в отдельный пул потоков, позволяя другимсопрограммы продолжаются, пока BeautifulSoup работает, и возобновляют текущую сопрограмму, как только это будет сделано.
С помощью небольшой дополнительной настройки вы можете расширить этот подход для использования многопроцессорной обработки и фактически использовать преимущества нескольких ядер:
from concurrent.futures import ProcessPoolExecutor
executor = ProcessPoolExecutor(8)
# Place the actual validation in a sync function that accepts a simple
# bytes object, and returns a bool (as opposed to a BeautifulSoup
# instance or such). Since these simple types are efficient to
# serialize, the function can run in a different process.
def do_validate(data):
try:
soup = BeautifulSoup(data, 'lxml')
except Exception:
print(traceback.format_exc())
return False
# the rest of your validation code goes here
# ...
return True
async def validate_page(session, url):
res = await fetch(session, url)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(executor, do_validate, res)
# fetch and validate_page_bounded are unchanged