Загрузка файлов через POST запросы в Python: полный разбор

Раздел: Сетевое программирование -> HTTP

Отправка файла через POST-запрос с библиотекой requests

Как отправить файл на сервер с помощью POST-запроса в Python?

Наиболее простой и распространенный способ - использовать метод requests.post() с параметром files. Данный параметр принимает словарь, где ключ - имя поля формы, а значение - открытый в бинарном режиме файловый объект. Пример:

import requests

url = 'https://httpbin.org/post'
with open('example.txt', 'rb') as f:
    response = requests.post(url, files={'file': f})

print(response.status_code)
print(response.json())

Python requests get (get-запрос через requests в python)

Код передает файл example.txt как часть multipart/form-data. Сервер httpbin.org отражает отправленные данные. В ответе будет видно содержимое файла.

Типичные ошибки:

  • Открытие файла в текстовом режиме ('r') вместо бинарного ('rb') - приводит к ошибке кодировки при загрузке бинарных данных.
  • Забыть закрыть файл вручную - решается использованием контекстного менеджера with.
  • Указание неверного url или отсутствие сети - вызывает исключение requests.exceptions.ConnectionError. Для обработки ошибок следует оборачивать вызов в try/except.
try:
    with open('data.bin', 'rb') as f:
        r = requests.post(url, files={'file': f}, timeout=10)
    r.raise_for_status()
except requests.exceptions.RequestException as e:
    print(f'Ошибка: {e}')

Get html python (получение html-содержимого через http в python)

Как отправить несколько файлов в одном запросе?

Для передачи нескольких файлов используется список кортежей или словарь с несколькими ключами. Вариант со списком позволяет задать одинаковое имя поля для разных файлов:

import requests

url = 'https://httpbin.org/post'
files = [
    ('images', ('photo1.jpg', open('photo1.jpg', 'rb'), 'image/jpeg')),
    ('images', ('photo2.jpg', open('photo2.jpg', 'rb'), 'image/jpeg'))
]
response = requests.post(url, files=files)
print(response.json())

Url запрос python (работа с url в python)

Возможная проблема:

Большое количество файлов может привести к превышению лимита памяти. В этом случае следует использовать потоковую отправку с параметром stream=True или отправлять файлы по одному.

Как передать файл вместе с обычными полями формы?

Поля формы задаются через параметр data. Библиотека автоматически объединит multipart-часть с обычными полями:

response = requests.post(
    url,
    files={'file': ('test.txt', open('test.txt', 'rb'), 'text/plain')},
    data={'username': 'alex', 'comment': 'hello'}
)

Python urllib request (отправка запросов с помощью urllib.request в python)

На сервере файл будет доступен как file, а поля формы - как username и comment.

Как отправить файл, не сохраняя его на диск (из памяти)?

Используется объект BytesIO из модуля io. Пример с генерацией CSV в памяти:

import requests
import io
import csv

url = 'https://httpbin.org/post'
output = io.StringIO()
writer = csv.writer(output)
writer.writerows([['a', 1], ['b', 2]])
output.seek(0)

response = requests.post(url, files={'data': ('report.csv', output.getvalue().encode('utf-8'))})
print(response.json())

Files upload python (загрузка файлов на сервер с помощью python (requests, flask))

Как указать произвольное имя файла и MIME-тип?

Значение для ключа в files может быть кортежем из трёх элементов: (имя_файла, содержимое, mime_тип):

response = requests.post(
    url,
    files={'file': ('mydata.csv', open('data.csv', 'rb'), 'text/csv')}
)

Python requests method (методы http-запросов в python (get, post, put, delete) с requests)

Это позволяет контролировать то, как файл будет отправлен, и что сервер увидит в заголовке Content-Disposition.

Как обработать ошибки соединения и таймауты?

Следует устанавливать параметр timeout и перехватывать исключения:

try:
    with open('large_file.zip', 'rb') as f:
        r = requests.post(url, files={'zip': f}, timeout=30)
    r.raise_for_status()
except requests.exceptions.Timeout:
    print('Сервер не ответил за отведённое время')
except requests.exceptions.RequestException as e:
    print(f'Ошибка: {e}')

Для больших файлов таймаут стоит увеличить, иначе запрос будет прерван. Также возможно использование параметра stream=True для отправки файла по частям.

- Python post file (отправка файла через post-запрос (requests.post(file)) в python)
- Python requests параметры (передача параметров в get/post запросах requests в python)
- Request session python (использование session в библиотеке requests (куки, заголовки) в python)

Расширенные примеры отправки файлов через POST

Ниже приведены более сложные сценарии, которые встречаются в реальных проектах.

Отправка файла с прогресс-баром (tqdm)

Для отслеживания прогресса загрузки большого файла можно использовать библиотеку tqdm. Устанавливается она отдельно (pip install tqdm). Ниже пример с собственной функцией генерации данных из файла:

Пример
import requests
from tqdm import tqdm

class FileWithProgress:
    def __init__(self, filename, mode='rb'):
        self.file = open(filename, mode)
        self.file_size = os.path.getsize(filename)
        self.progress = tqdm(total=self.file_size, unit='B', unit_scale=True)

    def __iter__(self):
        return self

    def __next__(self):
        chunk = self.file.read(1048576)  # 1 MB
        if not chunk:
            self.progress.close()
            self.file.close()
            raise StopIteration
        self.progress.update(len(chunk))
        return chunk

url = 'https://httpbin.org/post'
files = {'file': ('bigfile.iso', FileWithProgress('bigfile.iso'), 'application/octet-stream')}
response = requests.post(url, files=files)
print(response.status_code)
Предполагаемый вывод (прогресс-бар):
1.24GB [00:45, 28.2MB/s]

Важно: при таком подходе запрос не использует Content-Length, а применяется chunked transfer encoding. Сервер должен поддерживать такую передачу.

Использование сессии для нескольких файлов с авторизацией

При отправке файлов на защищённый ресурс удобно создать сессионный объект, который сохраняет куки и заголовки:

Пример
import requests

session = requests.Session()
session.auth = ('user', 'pass')  # базовая аутентификация
session.headers.update({'X-Custom': 'value'})

url = 'https://example.com/api/upload'
with open('file.txt', 'rb') as f:
    response = session.post(url, files={'file': f})
print(response.json())
Ответ сервера (пример):
{"status": "ok", "filename": "file.txt"}

Отправка файла вместе с JSON-данными

Иногда требуется передать и файл, и JSON-структуру в одном запросе. Для этого JSON-данные помещаются в параметр data в виде строки, а файл - в files:

Пример
import json

url = 'https://httpbin.org/post'
metadata = {
    'description': 'photo from vacation',
    'tags': ['sunset', 'beach']
}
with open('sunset.jpg', 'rb') as f:
    response = requests.post(
        url,
        data={'json': json.dumps(metadata)},
        files={'image': ('sunset.jpg', f, 'image/jpeg')}
    )
print(response.request.body)  # видно, как смешиваются части
Часть вывода (отладочная информация) может выглядеть как:
b'--...\r\nContent-Disposition: form-data...'

Отправка файла с использованием параметра params

Если сервер ожидает дополнительные URL-параметры при загрузке, они передаются через params. Пример с указанием версии API:

Пример
url = 'https://example.com/upload'
params = {'api_version': '2.0'}
with open('data.json', 'rb') as f:
    response = requests.post(url, files={'file': f}, params=params)
print(response.url) # https://example.com/upload?api_version=2.0

Отправка файла через потоковую передачу (stream=True)

Для больших файлов полезно установить stream=True, чтобы не загружать ответ целиком в память. Однако это не влияет на отправку, только на получение:

Пример
with open('large_file.mp4', 'rb') as f:
    response = requests.post(url, files={'video': f}, stream=True)
    # Читаем ответ порциями
    for chunk in response.iter_content(chunk_size=8192):
        if chunk:
            print('.', end='')  # имитация обработки ответа
Вывод: точки печатаются по мере поступления чанков.

Использование кастомных заголовков для multipart/form-data

Можно явно задать заголовок Content-Type с границей, но библиотека делает это автоматически. Пример, когда нужно изменить только код формы:

Пример
headers = {'Accept': 'application/json'}
response = requests.post(url, files={'file': open('doc.pdf', 'rb')}, headers=headers)

Распространенная ошибка в расширенных примерах:

При использовании FileWithProgress необходимо, чтобы итератор возвращал байты, а не строки. Также следует учесть, что сервер может не поддерживать chunked encoding - в таком случае нужно предварительно вычислить размер и передать его в заголовке Content-Length, что для произвольного итератора невозможно.

Асинхронная отправка файлов (aiohttp)

Хотя основная статья посвящена requests, стоит упомянуть асинхронный аналог. Пример с aiohttp для отправки нескольких файлов параллельно:

Пример
import aiohttp
import asyncio

async def upload_file(session, url, path):
    with open(path, 'rb') as f:
        data = aiohttp.FormData()
        data.add_field('file', f, filename=path.name)
        async with session.post(url, data=data) as resp:
            return await resp.json()

async def main():
    async with aiohttp.ClientSession() as session:
        results = await asyncio.gather(
            upload_file(session, 'https://httpbin.org/post', 'file1.txt'),
            upload_file(session, 'https://httpbin.org/post', 'file2.txt')
        )
        print(results)

asyncio.run(main())
Результат - список ответов от сервера для каждого файла.

Отправка файла через POST-запрос (requests.post(file)) в Python - comments

En
Python post file (python)