Классическая схема под это: очередь плюс фиксированное число рабочих. Тогда в памяти живёт ровно столько задач, сколько ты разрешил, остальное спокойно лежит строками в файле.
import asyncio, aiohttp
async def rabochiy(q, session, out):
while True:
url = await q.get()
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=30, sock_read=15)) as r:
out.append((url, r.status, len(await r.read())))
except Exception as e:
out.append((url, 0, repr(e)))
finally:
q.task_done()
async def main(ssylki, n=50):
q = asyncio.Queue(maxsize=n * 2)
out = []
conn = aiohttp.TCPConnector(limit=n, limit_per_host=8, ttl_dns_cache=300)
async with aiohttp.ClientSession(connector=conn) as session:
rab = [asyncio.create_task(rabochiy(q, session, out)) for _ in range(n)]
for u in ssylki:
await q.put(u)
await q.join()
for t in rab:
t.cancel()
return out
```
maxsize у очереди тут важнее семафора: await q.put(u) сам притормозит подачу, когда рабочие перестают успевать. Это и есть обратное давление, после него можно лить хоть миллион строк построчно из файла.
если не воспроизводится, значит не понял