ограничение количества одновременных запросов aiohttp


Я загружаю изображения с помощью aiohttp, и мне интересно, есть ли способ ограничить количество открытых запросов, которые еще не завершены. Вот код, который у меня сейчас есть:

async def get_images(url, session):

    chunk_size = 100

    # Print statement to show when a request is being made. 
    print(f'Making request to {url}')

    async with session.get(url=url) as r:
        with open('path/name.png', 'wb') as file:
            while True:
                chunk = await r.content.read(chunk_size)
                if not chunk:
                    break
                file.write(chunk)

# List of urls to get images from
urls = [...]

conn = aiohttp.TCPConnector(limit=3)
loop = asyncio.get_event_loop()
session = aiohttp.ClientSession(connector=conn, loop=loop)
loop.run_until_complete(asyncio.gather(*(get_images(url, session=session) for url in urls)))

Проблема в том, что я бросил инструкцию print, чтобы показать мне, когда делается каждый запрос, и он делает почти 21 запрос сразу, вместо 3, которые я хочу ограничить (т. е., как только изображение будет загружено, оно может перейти к следующему url в списке, чтобы получить). Мне просто интересно, что я я делаю здесь неправильно.

2 3

2 ответа:

Ваша настройка лимита работает правильно. Вы допустили ошибку при отладке.

Как указал Михаил Герасимов в комментарии, Вы поставили свой вызов print() не в то место - он должен быть внутри контекста session.get().

Чтобы быть уверенным, что ограничение соблюдается, я протестировал ваш код на простом сервере регистрации-и тест показывает, что сервер получает именно то количество соединений, которое вы задали в TCPConnector. Вот тест:

import asyncio
import aiohttp
loop = asyncio.get_event_loop()


class SilentServer(asyncio.Protocol):
    def connection_made(self, transport):
        # We will know when the connection is actually made:
        print('SERVER |', transport.get_extra_info('peername'))


async def get_images(url, session):

    chunk_size = 100

    # This log doesn't guarantee that we will connect,
    # session.get() will freeze if you reach TCPConnector limit
    print(f'CLIENT | Making request to {url}')

    async with session.get(url=url) as r:
        while True:
            chunk = await r.content.read(chunk_size)
            if not chunk:
                break

urls = [f'http://127.0.0.1:1337/{x}' for x in range(20)]

conn = aiohttp.TCPConnector(limit=3)
session = aiohttp.ClientSession(connector=conn, loop=loop)


async def test():
    await loop.create_server(SilentServer, '127.0.0.1', 1337)
    await asyncio.gather(*(get_images(url, session=session) for url in urls))

loop.run_until_complete(test())

Асинсио.Семафор решает именно этот вопрос.

В вашем случае это будет примерно так:

semaphore = asyncio.Semaphore(3)


async def get_images(url, session):

    async with semaphore:

        print(f'Making request to {url}')

        # ...

Вам также может быть интересно взглянуть на этот готовый к запуску код Пример, который демонстрирует, как работает семафор.