Вопрос-Ответ

How to send a "multipart/form-data" with requests in python?

Как отправить "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)

Пожалуйста, обратите внимание, что вам не нужно явно указывать какой-либо тип контента.

ПРИМЕЧАНИЕ: Хотел прокомментировать один из приведенных выше ответов, но не смог из-за низкой репутации, поэтому подготовил новый ответ здесь.

python python-requests