PythiloLesson 9 of 12 · progress kept in this browser

Semaphores and rate limits

gather starts everything at once. Against a real server, a thousand simultaneous requests get you throttled or blocked. An asyncio.Semaphore(n) holds n permits: async with sem: takes one, waiting if none are left, and gives it back when the block ends.

import asyncio

sem = asyncio.Semaphore(2)

async def fetch(n):
    async with sem:
        await asyncio.sleep(0.01)
        return n * n

async def main():
    jobs = [fetch(n) for n in range(5)]
    print(await asyncio.gather(*jobs))

asyncio.run(main())

All five tasks start, but only two at a time get past async with. The rest wait their turn.

Your task

Write async def crawl(urls, fetch, per_host) that calls fetch(url) for every URL at once, but never with more than per_host calls running for the same host, urllib.parse.urlsplit(url).hostname. Return the results as a list in the order of urls.