Как отправить "multipart / form-data" с запросами на python?
Как отправить multipart/form-data
with requests
на python? Как отправить файл, я понимаю, но как отправить данные формы этим методом понять не могу.
Переведено автоматически
Ответ 1
В принципе, если вы укажете files
параметр (словарь), то requests
вместо multipart/form-data
POST будет отправлено application/x-www-form-urlencoded
СООБЩЕНИЕ. Однако вы не ограничены использованием реальных файлов в этом словаре:
>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200
и httpbin.org позволяет узнать, с какими заголовками вы опубликовали; в response.json()
у нас есть:
>>> from pprint import pprint
>>> pprint(response.json()['headers'])
{'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close',
'Content-Length': '141',
'Content-Type': 'multipart/form-data; '
'boundary=c7cbfdd911b4e720f1dd8f479c50bc7f',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.21.0'}
И просто для наглядности: вам не следует не устанавливать Content-Type
заголовок при использовании files
параметра, оставьте это значение равным requests
, потому что для этого необходимо указать (уникальное) граничное значение в заголовке, которое соответствует значению, используемому в теле запроса.
Что еще лучше, вы можете дополнительно управлять именем файла, типом содержимого и дополнительными заголовками для каждой части, используя кортеж вместо объекта с одной строкой или байтами. Ожидается, что кортеж будет содержать от 2 до 4 элементов; имя файла, содержимое, необязательно тип содержимого и необязательный словарь дополнительных заголовков.
Я бы использовал форму кортежа с None
в качестве имени файла, чтобы filename="..."
параметр был удален из запроса для этих частей:
>>> files = {'foo': 'bar'}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--bb3f05a247b43eede27a124ef8b968c5
Content-Disposition: form-data; name="foo"; filename="foo"
bar
--bb3f05a247b43eede27a124ef8b968c5--
>>> files = {'foo': (None, 'bar')}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--d5ca8c90a869c5ae31f70fa3ddb23c76
Content-Disposition: form-data; name="foo"
bar
--d5ca8c90a869c5ae31f70fa3ddb23c76--
files
также может быть список кортежей с двумя значениями, если вам нужен порядок и / или несколько полей с одинаковым именем:
requests.post(
'http://requestb.in/xucj9exu',
files=(
('foo', (None, 'bar')),
('foo', (None, 'baz')),
('spam', (None, 'eggs')),
)
)
Если вы укажете оба files
и data
, то это зависит от значения data
того, что будет использоваться для создания тела сообщения. Если data
является строкой, будет использоваться только она; в противном случае используются оба data
и files
, причем элементы в data
перечислены первыми.
Также существует отличный requests-toolbelt
проект, который включает в себя расширенную поддержку Multipart. Он принимает определения полей в том же формате, что и files
параметр, но в отличие от requests
, по умолчанию параметр filename не задается. Кроме того, он может передавать запрос из открытых файловых объектов, где requests
сначала создаст тело запроса в памяти:
from requests_toolbelt.multipart.encoder import MultipartEncoder
mp_encoder = MultipartEncoder(
fields={
'foo': 'bar',
# plain file object, no filename or mime type produces a
# Content-Disposition header with just the part name
'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
}
)
r = requests.post(
'http://httpbin.org/post',
data=mp_encoder, # The MultipartEncoder is posted as data, don't use files=...!
# The MultipartEncoder provides the content-type header with the boundary:
headers={'Content-Type': mp_encoder.content_type}
)
Поля соответствуют тем же соглашениям; используйте кортеж из 2-4 элементов для добавления имени файла, части mime-типа или дополнительных заголовков. В отличие от files
параметра, не предпринимается попыток найти значение по умолчанию filename
, если вы не используете кортеж.
Ответ 2
Запросы изменились с тех пор, как были написаны некоторые из предыдущих ответов. Ознакомьтесь с этим вопросом на Github для получения более подробной информации и с этим комментарием для примера.
Короче говоря, files
параметр принимает словарь, ключом которого является имя поля формы, а значением является либо строка, либо кортеж длиной 2, 3 или 4, как описано в разделе ПУБЛИКАЦИЯ файла с кодировкой из нескольких частей в быстром запуске запросов:
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
В приведенном выше примере кортеж составлен следующим образом:
(filename, data, content_type, headers)
Если значение представляет собой просто строку, имя файла будет таким же, как ключ, как в следующем:
>>> files = {'obvius_session_id': '72c2b6f406cdabd578c5fd7598557c52'}
Content-Disposition: form-data; name="obvius_session_id"; filename="obvius_session_id"
Content-Type: application/octet-stream
72c2b6f406cdabd578c5fd7598557c52
Если значение является кортежем, а первая запись - None
свойство filename не будет включено:
>>> files = {'obvius_session_id': (None, '72c2b6f406cdabd578c5fd7598557c52')}
Content-Disposition: form-data; name="obvius_session_id"
Content-Type: application/octet-stream
72c2b6f406cdabd578c5fd7598557c52
Ответ 3
Вам нужно использовать files
параметр для отправки POST-запроса на составную форму, даже когда вам не нужно загружать какие-либо файлы.
Из исходного источника запросов:
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
...
:param files: (optional) Dictionary of ``'name': file-like-objects``
(or ``{'name': file-tuple}``) for multipart encoding upload.
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``,
3-tuple ``('filename', fileobj, 'content_type')``
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``,
where ``'content-type'`` is a string
defining the content type of the given file
and ``custom_headers`` a dict-like object
containing additional headers to add for the file.
Соответствующая часть: file-tuple
can be a
:
2-tuple
(filename, fileobj)3-tuple
(filename, fileobj, content_type)4-tuple
(filename, fileobj, content_type, custom_headers).
☝ Что может быть неочевидным, так это то, что
fileobj
может быть либо фактическим файловым объектом при работе с файлами, либо строкой при работе с обычными текстовыми полями.
Исходя из вышесказанного, простейший запрос на составную форму, который включает как файлы для загрузки, так и поля формы, будет выглядеть следующим образом:
import requests
multipart_form_data = {
'upload': ('custom_file_name.zip', open('myfile.zip', 'rb')),
'action': (None, 'store'),
'path': (None, '/path3')
}
response = requests.post('https://httpbin.org/post', files=multipart_form_data)
print(response.content)
☝ Обратите внимание на None
в качестве первого аргумента в кортеже для обычных текстовых полей — это заполнитель для поля filename, которое используется только для загрузки файлов, но для текстовых полей, передаваемых None
в качестве первого параметра, требуется для отправки данных.
Несколько полей с одинаковым именем
Если вам нужно опубликовать несколько полей с одинаковым именем, то вместо словаря вы можете определить свою полезную нагрузку в виде списка (или кортежа) кортежей:
multipart_form_data = (
('file2', ('custom_file_name.zip', open('myfile.zip', 'rb'))),
('action', (None, 'store')),
('path', (None, '/path3')),
('path', (None, '/path2')),
('path', (None, '/path3')),
)
API потоковых запросов
Если приведенный выше API недостаточно понятен для вас на python, то рассмотрите возможность использования requests toolbelt (pip install requests_toolbelt
), который является расширением модуля core requests, который обеспечивает поддержку потоковой загрузки файлов, а также MultipartEncoder, который можно использовать вместо files
, и который также позволяет вам определять полезную нагрузку в виде словаря, кортежа или списка.
MultipartEncoder
может использоваться как для составных запросов с фактическими полями загрузки, так и без них. Это должно быть присвоено параметру data
.
import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder
multipart_data = MultipartEncoder(
fields={
# a file upload field
'file': ('file.zip', open('file.zip', 'rb'), 'text/plain')
# plain text fields
'field0': 'value0',
'field1': 'value1',
}
)
response = requests.post('http://httpbin.org/post', data=multipart_data,
headers={'Content-Type': multipart_data.content_type})
Если вам нужно отправить несколько полей с одинаковым именем или если важен порядок расположения полей формы, то вместо словаря можно использовать кортеж или список:
multipart_data = MultipartEncoder(
fields=(
('action', 'ingest'),
('item', 'spam'),
('item', 'sausage'),
('item', 'eggs'),
)
)
Ответ 4
Вот простой фрагмент кода для загрузки отдельного файла с дополнительными параметрами с помощью запросов:
url = 'https://<file_upload_url>'
fp = '/Users/jainik/Desktop/data.csv'
files = {'file': open(fp, 'rb')}
payload = {'file_id': '1234'}
response = requests.put(url, files=files, data=payload, verify=False)
Пожалуйста, обратите внимание, что вам не нужно явно указывать какой-либо тип контента.
ПРИМЕЧАНИЕ: Хотел прокомментировать один из приведенных выше ответов, но не смог из-за низкой репутации, поэтому подготовил новый ответ здесь.