Загрузка файлов через POST запросы в Python: полный разбор
Отправка файла через 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 для отправки файла по частям.
Расширенные примеры отправки файлов через 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())
Результат - список ответов от сервера для каждого файла.