Как отправить «multipart / form-data» с запросами в python?

214

Как отправить multipart/form-dataс запросами в python? Как отправить файл, я понимаю, но как отправить данные формы этим способом не могу понять.

agrynchuk
источник
ваш вопрос не совсем понятен. Чего ты хочешь достичь? Вы хотите отправить «multipart / form-data» без загрузки файла в форму?
Ганс Тогда
4
Тот факт, что filesпараметр используется для обеих целей, является очень плохим API. Я поднял вопрос под названием « Отправка многокомпонентных данных» - нам нужен лучший API, чтобы это исправить. Если вы согласны с тем, что использование filesпараметра для отправки данных, состоящих из нескольких частей, в лучшем случае вводит в заблуждение, попросите изменить API в вышеуказанном выпуске.
Петр Доброгост
@PiotrDobrogost эта проблема закрыта. Не поощряйте людей комментировать закрытые вопросы, относящиеся к делу или иным образом.
Ян Стэплтон Кордаско
1
Неважно, я только что понял, что ваш комментарий был опубликован, прежде чем он был закрыт. Я ненавижу, как StackOverflow не хранит вещи в хронологическом порядке.
Ян Стэплтон Кордаско

Ответы:

168

По сути, если вы укажете filesпараметр (словарь), то requestsотправит multipart/form-dataPOST вместо application/x-www-form-urlencodedPOST. Вы не ограничены использованием реальных файлов в этом словаре, однако:

>>> 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'}

Более того, вы можете дополнительно контролировать имя файла, тип содержимого и дополнительные заголовки для каждой части, используя кортеж вместо единственной строки или объекта байтов. Ожидается, что кортеж будет содержать от 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что будет использоваться для создания тела POST. Если dataэто строка, будет использоваться только она; в противном случае оба dataи filesиспользуются, с элементами, dataперечисленными в первую очередь.

Существует также отличный requests-toolbeltпроект, который включает в себя расширенную поддержку Multipart . Он принимает определения полей в том же формате, что и filesпараметр, но в отличие от requestsнего, по умолчанию он не устанавливает параметр имени файла. Кроме того, он может передавать запрос от открытых файловых объектов, где 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значение по умолчанию, если вы не используете кортеж.

Мартейн Питерс
источник
3
Если используется files = {}, то заголовки = {'Content-Type': 'blah blah'} не должны использоваться!
Заки
5
@zaki: действительно, потому что multipart/form-dataContent-Type должен включать граничное значение, используемое для разделения частей в теле сообщения. Не установка Content-Typeзаголовка гарантирует, что requestsустанавливает его на правильное значение.
Мартин Питерс
Важное примечание: запрос будет отправляться только так, как multipart/form-dataесли бы значение files=true было, поэтому, если вам нужно отправить multipart/form-dataзапрос, но не включены какие-либо файлы, вы можете установить истинное, но бессмысленное значение, например {'':''}, и установить в data=теле запроса. Если вы делаете это, не предоставляйте Content-Typeзаголовок самостоятельно; requestsустановит это для вас. Вы можете увидеть проверку правды здесь: github.com/psf/requests/blob/…
Даниэль Ситунаяке
@DanielSitunayake нет необходимости в таком взломе. Просто поместите все поля в filesdict, они не должны быть файлами (просто убедитесь, что используете форму кортежа и установите имя файла в None). А еще лучше использовать requests_toolbeltпроект.
Мартин Питерс
Спасибо @MartijnPieters, трюк с формой кортежа великолепен! Попробую.
Даниэль Ситунаяке
107

Так как предыдущие ответы были написаны, запросы изменились. Посмотрите на ветку об ошибках на Github для более подробной информации и этот комментарий для примера.

Короче говоря, параметр files принимает a, dictключом которого является имя поля формы, а значением является строка или кортеж длиной 2, 3 или 4, как описано в разделе POST - файл с многочастным кодированием в запросах. быстрый старт:

>>> 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именем файла, свойство не будет включено:

>>> files = {'obvius_session_id': (None, '72c2b6f406cdabd578c5fd7598557c52')}

Content-Disposition: form-data; name="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52
runejuhl
источник
2
Что если вам нужно различать nameи filenameиметь несколько полей с одинаковыми именами?
Майкл
1
У меня такая же проблема, как у @Michael. Можете ли вы взглянуть на вопрос и предложить что-нибудь? [ссылка] ( stackoverflow.com/questions/30683352/… )
Shaardool
кто-то решил эту проблему с несколькими полями с одним именем?
user3131037
1
Хитрость для передачи пустой строки в качестве первого значения filesкортежа больше не работает: requests.post dataвместо этого вам нужно использовать параметр для отправки дополнительных не файловых multipart/form-dataпараметров
Лукас Кимон
1
Передача Noneвместо пустой строки, кажется, работает
Александр Блин
74

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 a2-tuple, .3-tupleor a4-tuple

Исходя из вышеизложенного, простейший многочастный запрос формы, который включает в себя как файлы для загрузки, так и поля формы, будет выглядеть следующим образом:

multipart_form_data = {
    'file2': ('custom_file_name.zip', open('myfile.zip', 'rb')),
    'action': (None, 'store'),
    'path': (None, '/path1')
}

response = requests.post('https://httpbin.org/post', files=multipart_form_data)

print(response.content)

Обратите внимание на Noneпервый аргумент в кортеже для текстовых полей - это заполнитель для поля имени файла, которое используется только для загрузки файлов, но для текстовых полей, передаваемых Noneв качестве первого параметра, требуется для представления данных ,

Несколько полей с одинаковым именем

Если вам нужно опубликовать несколько полей с одним и тем же именем, тогда вместо словаря вы можете определить свою полезную нагрузку в виде списка (или кортежа) кортежей:

multipart_form_data = (
    ('file2', ('custom_file_name.zip', open('myfile.zip', 'rb'))),
    ('action', (None, 'store')),
    ('path', (None, '/path1')),
    ('path', (None, '/path2')),
    ('path', (None, '/path3')),
)

API потоковых запросов

Если приведенный выше API не является достаточно питоническим для вас, рассмотрите возможность использования инструментального средства запросов ( pip install requests_toolbelt), которое является расширением модуля основных запросов, который обеспечивает поддержку потоковой передачи файлов, а также 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'),
           )
    )
ccpizza
источник
Спасибо тебе за это. Порядок ключей был важен для меня, и это очень помогло.
Великолепие
Удивительный. Необъяснимо, API, с которым я работаю, требует 2 разных значения для одного и того же ключа. Это потрясающе. Спасибо.
Аджон
@ccpizza, что на самом деле означает эта строка? > "('file.py', open ('file.py', 'rb'), 'text / plain')". Это не работает для меня :(
Денис Корейба
@DenisKoreyba: это пример поля загрузки файла, в котором предполагается, что названный файл file.pyнаходится в той же папке, что и ваш скрипт.
ccpizza
1
Вы можете использовать Noneвместо пустой строки. Тогда запросы не будут включать имя файла вообще. Так что вместо Content-Disposition: form-data; name="action"; filename=""этого будет Content-Disposition: form-data; name="action". Для меня было важно, чтобы сервер принимал эти поля как поля формы, а не как файлы.
Mitar
9

Вот простой фрагмент кода для загрузки одного файла с дополнительными параметрами с использованием запросов:

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)

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

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

Jainik
источник
4

Вам нужно использовать nameатрибут файла загрузки, который находится в HTML-коде сайта. Пример:

autocomplete="off" name="image">

Видишь name="image">? Вы можете найти его в HTML-коде сайта для загрузки файла. Вы должны использовать его для загрузки файла сMultipart/form-data

сценарий:

import requests

site = 'https://prnt.sc/upload.php' # the site where you upload the file
filename = 'image.jpg'  # name example

Здесь вместо изображения добавьте название загружаемого файла в HTML

up = {'image':(filename, open(filename, 'rb'), "multipart/form-data")}

Если при загрузке требуется нажать кнопку загрузки, вы можете использовать вот так:

data = {
     "Button" : "Submit",
}

Затем запустите запрос

request = requests.post(site, files=up, data=data)

И готово, файл успешно загружен

Скиллер Дз
источник
3

Отправить ключ и значение multipart / form-data

команда curl:

curl -X PUT http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' \
-F taskStatus=1

Python- запросы - более сложные POST-запросы :

    updateTaskUrl = "http://127.0.0.1:8080/api/xxx"
    updateInfoDict = {
        "taskStatus": 1,
    }
    resp = requests.put(updateTaskUrl, data=updateInfoDict)

Отправить файл multipart / form-data

команда curl:

curl -X POST http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' \
-F file=@/Users/xxx.txt

запросы Python - POST файл, закодированный в нескольких частях :

    filePath = "/Users/xxx.txt"
    fileFp = open(filePath, 'rb')
    fileInfoDict = {
        "file": fileFp,
    }
    resp = requests.post(uploadResultUrl, files=fileInfoDict)

вот и все.

crifan
источник
-1

Вот фрагмент кода Python, который вам нужен для загрузки одного большого файла в виде составных данных. С промежуточным программным обеспечением NodeJs Multer, работающим на стороне сервера.

import requests
latest_file = 'path/to/file'
url = "http://httpbin.org/apiToUpload"
files = {'fieldName': open(latest_file, 'rb')}
r = requests.put(url, files=files)

Для серверной стороны, пожалуйста, проверьте документацию multer по адресу: https://github.com/expressjs/multer, здесь одиночное поле ('fieldName') используется для принятия одного файла, как в:

var upload = multer().single('fieldName');
vinaymk
источник