Як надіслати "багаточастинні / форми-дані" із запитами в python?


211

Як надіслати multipart/form-dataзапит у python? Як надіслати файл, я розумію, але як надіслати дані форми цим методом зрозуміти не можу.


ваше запитання не зовсім зрозуміло. Чого ти хочеш досягти? Ви бажаєте надіслати "багатопотужні / форму-дані" без завантаження файлу у форму?
Тоді Ганс

4
Те, що filesпараметр використовується для виконання обох, є дуже поганим API. Я порушив проблему під назвою Надсилання даних з кількох частин - для її виправлення нам потрібен кращий API . Якщо ви погоджуєтесь, що використання filesпараметра для надсилання даних mulitpart в кращому випадку вводить в оману, будь ласка, попросіть змінити API у вищевказаному випуску.
Пьотр Доброгост

@PiotrDobrogost ця проблема закрита. Не закликайте людей коментувати закриті питання, актуальні чи іншим чином.
Ian Stapleton Cordasco

1
Не забудьте, я щойно зрозумів, що ваш коментар був розміщений ще до його закриття. Я ненавиджу, як StackOverflow не підтримує речі в хронологічному порядку.
Ian Stapleton Cordasco

Відповіді:


166

В основному, якщо ви вкажете 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
Якщо використовуються файли = {}, то заголовки = {'Content-Type': 'blah blah'} не повинні використовуватися!
заки

5
@zaki: дійсно, тому що multipart/form-dataContent-Type повинен містити граничне значення, яке використовується для розмежування частин у тілі публікації. Якщо не встановити Content-Typeзаголовок, це гарантує requestsйого правильне значення.
Martijn Pieters

Важлива примітка: запит буде надіслано лише як multipart/form-dataби значення files=truthy, тому, якщо вам потрібно надіслати multipart/form-dataзапит, але не включаючи жодні файли, ви можете встановити неправдоподібне, але безглузде значення, наприклад {'':''}, та встановити data=з його органом запиту. Якщо ви це робите, не надайте Content-Typeзаголовка самостійно; requestsвстановить це вам. Перевірити істинність можна тут: github.com/psf/requests/blob/…
Даніель Ситуанаяке

@DanielSitunayake в такому злому немає потреби. Просто покладіть всі поля в filesдикт, вони не повинні бути файлами (просто переконайтеся, що використовуєте форму кортежу та встановіть ім'я файлу None). Ще краще використовувати requests_toolbeltпроект.
Martijn Pieters

Спасибі @MartijnPieters, трюк з формою кортежу чудовий! Спробую це.
Даніель Сітуанаяке

107

Оскільки попередні відповіді були написані, запити змінилися. Перегляньте потік помилок у Github для більш детальної інформації та цей коментар для прикладу.

Коротше кажучи, параметр файли приймає 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

2
Що робити , якщо вам потрібно розрізняти nameі , filenameа й мати кілька полів з однаковим ім'ям?
Михайло

1
У мене є подібна проблема, як @Michael. Ви можете поглянути на питання і щось запропонувати? [Посилання] ( stackoverflow.com/questions/30683352 / ... )
Shaardool

хтось вирішив цю проблему, маючи кілька полів з однаковою назвою?
користувач3131037

1
Трюк пропустити en порожній рядок, оскільки перше значення filesкортежу більше не працює: requests.post dataзамість цього вам потрібно скористатися параметром, щоб надіслати додаткові нефайлові multipart/form-dataпараметри
Лукас Кімон

1
Проходження Noneзамість порожньої рядка, здається, працює
Олександр Блін

73

Вам потрібно скористатися 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'),
           )
    )

Дякую за це. Порядок клавіш був важливим для мене, і це дуже допомогло.
Чудовий

Дивовижний. Незрозуміло, що api, з яким я працюю, вимагає 2 різних значень для одного ключа. Це дивно. Дякую.
аджон

@ccpizza, що насправді означає цей рядок? > "('file.py', відкрити ('file.py', 'rb'), 'text / plain')". Це не працює для мене :(
Денис Корейба

@DenisKoreyba: це приклад поля для завантаження файлу, який передбачає, що названий файл file.pyзнаходиться в тій же папці, що і ваш сценарій.
ccpizza

1
Ви можете використовувати Noneзамість порожнього рядка. Тоді запити взагалі не включатимуть ім’я файлу. Так замість Content-Disposition: form-data; name="action"; filename=""цього буде Content-Disposition: form-data; name="action". Для мене це було критично важливо, щоб сервер сприйняв ці поля як поля форми, а не як файли.
Мітар

8

Ось простий фрагмент коду для завантаження одного файлу з додатковими параметрами за допомогою запитів:

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)

Зауважте, що вам не потрібно чітко вказувати будь-який тип вмісту.

ПРИМІТКА. Хотіли прокоментувати одну з вищезазначених відповідей, але не змогли через низьку репутацію.


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

Надішліть ключ та величину даних / форм-даних та значення

команда 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)

Надішліть файл із множинними формами / формами даних

команда 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- запити - розмістити файл з кодованим багаточастковим файлом :

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

це все.


-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)

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

var upload = multer().single('fieldName');
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.