Из адекватных
Вариант 1: Создавать кучку воркеров через ThreadPoolExecutor, закидывать им очередь урлов
Вариант 2: aiohttp, создавать кучку тасков-воркеров, закидывать им очередь (asyncio.Queue) урлов
Вариант 3: aiohttp, запускать все 30к одновременно, с помощью asyncio.Semaphore задавать, сколько максимум может быть запросов одновременно
Вариант №3 самый простой в реализации и оптимальный. Примерно на 15 строк кода. Но будет кушать чуть больше памяти, чем вариант 2. Вариант 1 самый тяжёлый из-за того, что каждый тред в ОС будет кушать примерно по 2мб памяти, зато синхронный.