Загрузка данных


import asyncio
import time
from typing import List
# asyncio    - для асинхронного программирования
# time       - замер времени выполнения
# List       - аннотация для списков

import aiohttp      # асинхронный HTTP-клиент (pip install aiohttp)
import requests     # синхронный HTTP-клиент (pip install requests)


# ===========================================================================
# СИНХРОННОЕ СКАЧИВАНИЕ (один за другим)
# ===========================================================================

def download_sync(urls: List[str]) -> List[bytes]:
    """Скачивает все файлы по очереди (блокирует поток)."""
    results = []                            # сюда будем складывать скачанные байты
    start = time.perf_counter()             # точное время начала (в секундах)

    for i, url in enumerate(urls, start=1): # i=1,2,3... url=ссылка на файл
        print(f"[SYNC] start {i}: {url}")   # лог: начали скачивать i-й файл
        
        resp = requests.get(url)            # синхронный запрос (блокирует до ответа)
        resp.raise_for_status()             # если ошибка HTTP 4xx/5xx → выбросит исключение
        
        data = resp.content                  # получаем содержимое ответа (байты)
        results.append(data)                 # добавляем в список результатов
        
        print(f"[SYNC] done  {i}: size={len(data)} bytes")  # лог: закончили

    total = time.perf_counter() - start     # общее время выполнения
    print(f"[SYNC] total time: {total:.2f} s\n")  # выводим время с 2 знаками после запятой
    return results                          # возвращаем все скачанные файлы


# ===========================================================================
# АСИНХРОННОЕ СКАЧИВАНИЕ ОДНОГО ФАЙЛА С СЕМАФОРОМ
# ===========================================================================

async def fetch_one(
    session: aiohttp.ClientSession,     # общий HTTP-клиент для всех запросов
    url: str,                           # ссылка на файл
    sem: asyncio.Semaphore,             # семафор для ограничения параллелизма
    index: int,                         # номер задачи (для логов)
) -> bytes:
    """Скачивает один файл асинхронно с контролем семафора."""
    
    async with sem:                     # ← КЛЮЧЕВОЕ: одновременно в этом блоке будет максимум K задач!
        # sem выдаёт "разрешения" (permits), их всего K штук
        print(f"[ASYNC] start {index}: {url} (free permits={sem._value})")
        # sem._value - сколько разрешений осталось свободными
        
        async with session.get(url) as resp:  # асинхронный HTTP-запрос (не блокирует)
            resp.raise_for_status()           # проверяем статус ответа
            
            data = await resp.read()          # асинхронно читаем тело ответа
            # await - "подождать", но не блокирует весь поток asyncio
            
        print(f"[ASYNC] done  {index}: size={len(data)} bytes")
        return data                        # возвращаем байты файла


# ===========================================================================
# АСИНХРОННОЕ СКАЧИВАНИЕ ВСЕХ ФАЙЛОВ С ЛИМИТОМ K
# ===========================================================================

async def download_async(urls: List[str], k: int) -> List[bytes]:
    """Скачивает N файлов, но максимум k одновременно."""
    start = time.perf_counter()           # время начала
    sem = asyncio.Semaphore(k)            # семафор с лимитом k одновременных задач
    
    async with aiohttp.ClientSession() as session:  # один клиент для всех запросов
        # создаём задачи сразу на все N файлов
        tasks = [
            asyncio.create_task(fetch_one(session, url, sem, i))
            # create_task - сразу запускает корутину в фоне
            for i, url in enumerate(urls, start=1)
        ]
        
        # ждём завершения ВСЕХ задач, результаты вернутся в том же порядке
        results = await asyncio.gather(*tasks)
        # gather(*tasks) = жди все задачи и собери результаты списком

    total = time.perf_counter() - start
    print(f"[ASYNC] total time (K={k}): {total:.2f} s\n")
    return results


# ===========================================================================
# ТОЧКА ВХОДА - СРАВНЕНИЕ
# ===========================================================================

if __name__ == "__main__":
    N = 10                    # количество файлов для скачивания
    K = 3                     # максимум одновременных асинхронных задач
    
    test_url = "https://httpbin.org/delay/1"  # тестовая ссылка, отвечает 1 секунду
    urls = [test_url] * N     # список из N одинаковых ссылок
    
    print("=== СИНХРОННО (по очереди) ===")
    sync_results = download_sync(urls)
    
    print("=== АСИНХРОННО (лимит K=3) ===")
    async_results = asyncio.run(download_async(urls, K))
    # asyncio.run - запускает асинхронную функцию в новом event loop
    
    print(f"Синхронно скачали: {len(sync_results)} файлов")
    print(f"Асинхронно скачали: {len(async_results)} файлов")