Загрузка файлов через HTTP-запросы в Python

Раздел: Python -> Requests

Библиотека requests предоставляет удобные инструменты для загрузки файлов по протоколу HTTP. В зависимости от размера файла, требований к памяти и необходимости контроля над процессом применяются разные подходы. Далее рассмотрены основные способы с примерами кода и указанием типичных проблем.

Основные методы скачивания файлов

Как сохранять большие файлы без перегрузки памяти?

Наиболее эффективное решение для файлов любого размера – потоковая загрузка с параметром stream=True. Данные читаются частями (чанками), что позволяет обрабатывать объёмы, превышающие доступную оперативную память.


import requests

url = "https://example.com/largefile.iso"
response = requests.get(url, stream=True)
response.raise_for_status()

with open("largefile.iso", "wb") as f:
    for chunk in response.iter_content(chunk_size=8192):
        if chunk:
            f.write(chunk)
  

Python 2 requests (библиотека requests в python 2)

Размер чанка (8192 байт) является разумным компромиссом между скоростью и накладными расходами. Метод iter_content автоматически декодирует сжатые данные, если сервер передаёт gzip или deflate.

Возможные проблемы: отсутствие stream=True приводит к загрузке всего файла в память; забытый raise_for_status() может сохранить сообщение об ошибке вместо файла; при записи на диск без проверки прав может возникнуть PermissionError.

Как скачать небольшой файл целиком?

Для файлов размером до нескольких десятков мегабайт применяется свойство response.content, возвращающее полное содержимое ответа. Этот способ прост и не требует циклов.


import requests

response = requests.get("https://example.com/small.png")
response.raise_for_status()
with open("small.png", "wb") as f:
    f.write(response.content)
  

Python 3 requests (библиотека requests в python 3)

Подходит только когда размер заведомо мал. При больших объёмах возрастает риск MemoryError и замедления работы программы.

Частая ошибка – сохранение пустого файла при коде ответа 404. Всегда следует проверять статус перед записью.

Как гарантировать успешность запроса перед сохранением?

Метод raise_for_status() вызывает исключение, если HTTP-статус свидетельствует об ошибке (4xx или 5xx). Это позволяет избежать записи некорректного содержимого.


import requests

response = requests.get("https://example.com/document.pdf")
try:
    response.raise_for_status()
except requests.exceptions.HTTPError as err:
    print(f"Ошибка HTTP: {err}")
else:
    with open("document.pdf", "wb") as f:
        f.write(response.content)
  

Python requests url (выполнение запроса по url с помощью requests в python)

Цель – обнаружить проблемы (например, 404, 403) на раннем этапе и обработать их отдельно.

Исключение не обрабатывается автоматически; если не использовать try/except, программа упадёт. Полезно оборачивать вызов в блок try для логирования.

Как ограничить время ожидания ответа?

Параметр timeout задаёт максимальное время в секундах для установки соединения и получения первого байта. Предотвращает зависание программы при медленных или недоступных серверах.


import requests

try:
    response = requests.get("https://slow-server.com/file", timeout=5)
    with open("file.bin", "wb") as f:
        f.write(response.content)
except requests.exceptions.Timeout:
    print("Сервер не ответил за отведённое время.")
  

Python requests headers (заголовки запросов в python requests)

Таймаут можно задать как число (для всего запроса) или кортеж из двух чисел – на соединение и на чтение.

Слишком маленький таймаут приведёт к частым ложным ошибкам. Рекомендуется устанавливать не менее 5–10 секунд для обычных сценариев.

Как имитировать браузер при скачивании?

Некоторые серверы блокируют запросы от скриптов. Добавление заголовка User-Agent с типичной строкой браузера помогает обойти такую защиту.


import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
response = requests.get("https://protected-site.com/file.pdf", headers=headers)
response.raise_for_status()
with open("file.pdf", "wb") as f:
    f.write(response.content)
  

Python requests exceptions (исключения в python requests)

Помимо User-Agent иногда требуются заголовки Referer, Accept и другие.

Заголовок User-Agent не гарантирует обход всех блокировок. Может потребоваться использование сессии с куками.

Как отображать прогресс загрузки?

Библиотека tqdm позволяет создать простой индикатор выполнения. Необходимо получить размер контента из заголовка Content-Length и передавать его в tqdm вместе с итератором чанков.


import requests
from tqdm import tqdm

url = "https://example.com/bigfile.bin"
response = requests.get(url, stream=True)
total = int(response.headers.get("content-length", 0))

with open("bigfile.bin", "wb") as f, tqdm(
    desc="Загрузка",
    total=total,
    unit="B",
    unit_scale=True,
    unit_divisor=1024
) as bar:
    for chunk in response.iter_content(chunk_size=8192):
        if chunk:
            f.write(chunk)
            bar.update(len(chunk))
  

Python requests codes (коды ответов http в python requests)

При отсутствии заголовка Content-Length прогресс не отображается. В таких случаях можно установить total=None, но индикатор будет неопределённым.

Не все серверы передают Content-Length. Для потоковых данных (например, live-видео) этот метод не подходит.

Как возобновить прерванную загрузку?

Если файл уже частично скачан, можно использовать заголовок Range для запроса недостающих байтов. Сервер должен поддерживать частичные запросы (код 206).


import requests
import os

url = "https://example.com/resume_file.bin"
partial_file = "resume_file.bin"

# Определяем уже скачанный размер
if os.path.exists(partial_file):
    downloaded = os.path.getsize(partial_file)
else:
    downloaded = 0

headers = {"Range": f"bytes={downloaded}-"}
response = requests.get(url, headers=headers, stream=True)

if response.status_code == 206:
    with open(partial_file, "ab") as f:
        for chunk in response.iter_content(chunk_size=8192):
            if chunk:
                f.write(chunk)
    print("Загрузка продолжена.")
else:
    print("Сервер не поддерживает докачку, скачиваем заново.")
    # альтернативное поведение
  

Цель – экономия трафика при нестабильном соединении. Код проверяет существующий файл и добавляет новые данные в конец.

Если сервер не возвращает 206, необходимо либо скачать файл с нуля, либо обработать ошибку. Важно учитывать, что заголовок Content-Length в ответе будет оставшейся частью, а не полным размером.

- работа с requests python (работа с библиотекой requests в python)
- Requests python скачать (скачивание файлов с помощью requests в python)
- Python types requests (типы запросов в requests python)

Скачивание с использованием сессии для сохранения кук

Объект Session позволяет переносить куки и заголовки между запросами, что полезно при загрузке файлов с сайтов, требующих авторизации.

Пример

import requests

with requests.Session() as session:
    # Авторизация (пример с Basic Auth)
    session.auth = ("user", "pass")
    # Или через форму
    # session.post("https://example.com/login", data={"login": "u", "password": "p"})
    
    url = "https://example.com/protected/file.txt"
    response = session.get(url, stream=True)
    response.raise_for_status()
    
    with open("file.txt", "wb") as f:
        for chunk in response.iter_content(8192):
            if chunk:
                f.write(chunk)
# Результат: файл file.txt успешно скачан из защищённого раздела

Сессия автоматически обрабатывает редиректы и сохраняет куки после логина.

Скачивание с авторизацией через Bearer token

API часто используют токены в заголовке Authorization. Пример получения файла с GitHub Releases.

Пример

import requests

token = "ghp_xxxxxxxxxxxx"
headers = {"Authorization": f"Bearer {token}"}

url = "https://api.github.com/repos/owner/repo/releases/assets/12345"
response = requests.get(url, headers=headers, stream=True)
response.raise_for_status()

# Имя файла может быть из заголовка Content-Disposition
import re
cd = response.headers.get("Content-Disposition", "")
filename = re.findall('filename="(.+)"', cd)[0] if cd else "downloaded_file"

with open(filename, "wb") as f:
    for chunk in response.iter_content(8192):
        if chunk:
            f.write(chunk)
# Результат: файл (например, release.zip) сохранён с оригинальным именем

Извлечение имени из Content-Disposition позволяет сохранить файл с правильным расширением.

Скачивание с проверкой целостности через хэш

После загрузки можно вычислить хэш (например, MD5 или SHA256) и сверить с известным значением для гарантии отсутствия ошибок.

Пример

import requests
import hashlib

url = "https://example.com/debian.iso"
expected_hash = "a1b2c3d4e5f6..."  # SHA256 из официального сайта

response = requests.get(url, stream=True)
response.raise_for_status()

sha256 = hashlib.sha256()
with open("debian.iso", "wb") as f:
    for chunk in response.iter_content(8192):
        if chunk:
            f.write(chunk)
            sha256.update(chunk)

computed_hash = sha256.hexdigest()
if computed_hash == expected_hash:
    print("Хэш совпадает, файл корректен.")
else:
    print("Ошибка: хэш не совпадает, файл повреждён.")
# Результат: вывод сообщения о совпадении или несовпадении хэша

Важно: хэширование больших файлов через update() не требует загрузки всего файла в память.

Параллельная загрузка нескольких файлов с помощью ThreadPoolExecutor

При необходимости скачать много файлов одновременно используется пул потоков из стандартной библиотеки concurrent.futures.

Пример

import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import os

def download_file(url, dest_folder):
    local_filename = os.path.join(dest_folder, url.split("/")[-1])
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, "wb") as f:
            for chunk in r.iter_content(8192):
                if chunk:
                    f.write(chunk)
    return local_filename

urls = [
    "https://example.com/photo1.jpg",
    "https://example.com/photo2.jpg",
    "https://example.com/photo3.jpg",
]

with ThreadPoolExecutor(max_workers=4) as executor:
    future_to_url = {executor.submit(download_file, url, "./downloads"): url for url in urls}
    for future in as_completed(future_to_url):
        url = future_to_url[future]
        try:
            result = future.result()
            print(f"Скачан: {result}")
        except Exception as e:
            print(f"Ошибка для {url}: {e}")
# Вывод:
# Скачан: ./downloads/photo1.jpg
# Скачан: ./downloads/photo2.jpg
# Скачан: ./downloads/photo3.jpg

Число потоков можно регулировать. Важно: параллельные запросы к одному серверу могут быть ограничены (rate limiting).

Скачивание с проверкой ETag для предотвращения повторной загрузки одинаковых файлов

Заголовок ETag представляет версию файла. Сохранив его в локальном кэше, можно в следующий раз отправить заголовок If-None-Match и получить ответ 304 Not Modified, если файл не изменился.

Пример

import requests
import json

url = "https://example.com/versioned_data.json"
cache_file = "etag_cache.json"

# Читаем сохранённый ETag
etag = None
try:
    with open(cache_file, "r") as cf:
        cache = json.load(cf)
        etag = cache.get(url)
except FileNotFoundError:
    pass

headers = {}
if etag:
    headers["If-None-Match"] = etag

response = requests.get(url, headers=headers)
if response.status_code == 304:
    print("Файл не изменился, используем локальную копию.")
else:
    with open("versioned_data.json", "wb") as f:
        f.write(response.content)
    # Сохраняем новый ETag
    new_etag = response.headers.get("ETag")
    if new_etag:
        with open(cache_file, "w") as cf:
            json.dump({url: new_etag}, cf)
        print("Файл обновлён.")
# Результат: либо сообщение "Файл не изменился", либо файл перезаписан

Аналогично можно использовать заголовок If-Modified-Since с датой Last-Modified.

Скачивание файлов с помощью requests в Python - comments

En
Requests python скачать (python)