Bagaimana cara mengirim "multipart / formulir-data" dengan permintaan dengan python?

214

Bagaimana cara mengirim multipart/form-datapermintaan dengan python? Cara mengirim file, saya mengerti, tetapi cara mengirim data formulir dengan metode ini tidak bisa mengerti.

agrynchuk
sumber
pertanyaan Anda tidak terlalu jelas. Apa yang ingin Anda capai? Apakah Anda ingin mengirim "multipart / formulir-data" tanpa unggahan file dalam formulir?
Hans Then
4
Fakta bahwa filesparameter yang digunakan untuk melakukan keduanya adalah API yang sangat buruk. Saya mengangkat masalah berjudul Mengirim data multi-bagian - kita perlu API yang lebih baik untuk memperbaikinya. Jika Anda setuju bahwa menggunakan filesparameter untuk mengirim data mulitpart paling menyesatkan, silakan minta untuk mengubah API dalam masalah di atas.
Piotr Dobrogost
@PiotrDobrogost bahwa masalah telah ditutup. Jangan mendorong orang untuk mengomentari masalah tertutup, relevan atau sebaliknya.
Ian Stapleton Cordasco
1
Nevermind, saya baru menyadari komentar Anda diposting sebelum ditutup. Saya benci bagaimana StackOverflow tidak menjaga hal-hal dalam urutan kronologis.
Ian Stapleton Cordasco

Jawaban:

168

Pada dasarnya, jika Anda menentukan filesparameter (kamus), maka requestsakan mengirim multipart/form-dataPOST bukannya application/x-www-form-urlencodedPOST. Namun, Anda tidak terbatas menggunakan file aktual dalam kamus itu:

>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200

dan httpbin.org memberi tahu Anda tajuk apa yang Anda poskan; di response.json()kita miliki:

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

Lebih baik lagi, Anda dapat lebih mengontrol nama file, tipe konten, dan header tambahan untuk setiap bagian dengan menggunakan tuple alih-alih objek string atau byte tunggal. Tuple diharapkan mengandung antara 2 dan 4 elemen; nama file, konten, opsional jenis konten, dan kamus opsional header lebih lanjut.

Saya akan menggunakan formulir tuple dengan Nonesebagai nama file, sehingga filename="..."parameternya dihapus dari permintaan untuk bagian-bagian itu:

>>> 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 juga bisa menjadi daftar dua nilai tupel, jika Anda perlu memesan dan / atau beberapa bidang dengan nama yang sama:

requests.post(
    'http://requestb.in/xucj9exu',
    files=(
        ('foo', (None, 'bar')),
        ('foo', (None, 'baz')),
        ('spam', (None, 'eggs')),
    )
)

Jika Anda menentukan baik filesdan data, maka itu tergantung pada nilai dari dataapa yang akan digunakan untuk membuat tubuh POST. Jika datasebuah string, hanya itu yang akan digunakan; jika tidak keduanya digunakan datadan filesdigunakan, dengan elemen-elemen yang datatercantum terlebih dahulu.

Ada juga requests-toolbeltproyek unggulan , yang mencakup dukungan Multipart tingkat lanjut . Dibutuhkan definisi bidang dalam format yang sama dengan filesparameter, tetapi tidak seperti requestsitu, standarnya adalah tidak menyetel parameter nama file. Selain itu, ia dapat mengalirkan permintaan dari objek file terbuka, di mana requestspertama-tama akan membangun tubuh permintaan dalam memori:

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

Fields mengikuti konvensi yang sama; gunakan tuple dengan antara 2 dan 4 elemen untuk menambahkan nama file, tipe tajuk atau tajuk ekstra. Berbeda dengan filesparameter, tidak ada upaya yang dilakukan untuk menemukan filenamenilai default jika Anda tidak menggunakan tuple.

Martijn Pieters
sumber
3
Jika file = {} digunakan maka tajuk = {'Jenis-Konten': 'blah blah'} tidak boleh digunakan!
zaki
5
@zaki: memang, bacause multipart/form-dataJenis-Konten harus menyertakan nilai batas yang digunakan untuk menghapus bagian-bagian di badan pos. Tidak mengatur Content-Typetajuk memastikan bahwa requestsset ke nilai yang benar.
Martijn Pieters
Catatan penting: permintaan hanya akan dikirim seolah- multipart/form-dataolah nilai dari files=kebenaran, jadi jika Anda perlu mengirim multipart/form-datapermintaan tetapi tidak termasuk file apa pun, Anda dapat menetapkan nilai yang benar tetapi tidak berarti seperti {'':''}, dan diatur data=dengan badan permintaan Anda. Jika Anda melakukan ini, jangan berikan Content-Typetajuk sendiri; requestsakan mengaturnya untuk Anda. Anda dapat melihat kebenarannya di sini: github.com/psf/requests/blob/…
Daniel Situnayake
@DanielSitunayake tidak perlu melakukan peretasan seperti itu. Masukkan saja semua bidang dalam filesdikt, mereka tidak harus berupa file (pastikan untuk menggunakan formulir tuple dan atur nama file ke None). Lebih baik lagi, gunakan requests_toolbeltproyek.
Martijn Pieters
Terima kasih @MartijnPieters, trik dengan bentuk tuple sangat bagus! Akan mencobanya.
Daniel Situnayake
107

Karena jawaban sebelumnya ditulis, permintaan telah berubah. Lihat utas bug di Github untuk lebih detail dan komentar ini sebagai contoh.

Singkatnya, parameter file mengambil dictdengan kunci menjadi nama bidang formulir dan nilainya berupa string atau tuple 2, 3 atau 4-panjang, seperti yang dijelaskan di bagian POST File Multipart-Encoded dalam permintaan mulai cepat:

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

Di atas, tupel terdiri sebagai berikut:

(filename, data, content_type, headers)

Jika nilainya hanya string, nama file akan sama dengan kunci, seperti berikut ini:

>>> files = {'obvius_session_id': '72c2b6f406cdabd578c5fd7598557c52'}

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

72c2b6f406cdabd578c5fd7598557c52

Jika nilainya tupel dan entri pertama adalah Noneproperti nama file tidak akan disertakan:

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

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

72c2b6f406cdabd578c5fd7598557c52
runejuhl
sumber
2
Bagaimana jika Anda perlu membedakan namedan filenametetapi juga memiliki beberapa bidang dengan nama yang sama?
Michael
1
Saya punya masalah serupa dengan @Michael. Bisakah Anda melihat pertanyaannya dan menyarankan sesuatu? [tautan] ( stackoverflow.com/questions/30683352/… )
Shaardool
apakah seseorang memecahkan masalah ini dengan memiliki beberapa bidang dengan nama yang sama?
user3131037
1
Trik untuk meneruskan string kosong sebagai nilai pertama sebuah filestuple tidak berfungsi lagi: Anda harus menggunakan requests.post dataparameter alih-alih mengirim multipart/form-dataparameter non-file tambahan
Lucas Cimon
1
Melewati Nonealih-alih seutas tali kosong sepertinya berhasil
Alexandre Blin
74

Anda perlu menggunakan filesparameter untuk mengirim permintaan POST bentuk multi-bagian bahkan ketika Anda tidak perlu mengunggah file apa pun.

Dari sumber permintaan asli :

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.

Bagian yang relevan adalah: file-tuple can be a2-tuple, .3-tupleor a4-tuple

Berdasarkan hal di atas, permintaan formulir multi-bagian paling sederhana yang mencakup file untuk diunggah dan bidang formulir akan terlihat seperti ini:

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)

Catat Nonesebagai argumen pertama dalam tupel untuk bidang teks biasa - ini adalah penampung untuk bidang nama file yang hanya digunakan untuk unggahan file, tetapi untuk bidang teks yang lewat Nonekarena parameter pertama diperlukan agar data yang akan dikirim .

Beberapa bidang dengan nama yang sama

Jika Anda perlu memposting beberapa bidang dengan nama yang sama, alih-alih sebagai kamus, Anda dapat menentukan payload Anda sebagai daftar (atau tuple) dari tuple:

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 permintaan streaming

Jika API di atas tidak cukup pythonic untuk Anda, maka pertimbangkan untuk menggunakan permintaan toolbelt ( pip install requests_toolbelt) yang merupakan perpanjangan dari modul permintaan inti yang menyediakan dukungan untuk streaming unggah file serta MultipartEncoder yang dapat digunakan sebagai penggantifiles , dan yang juga memungkinkan Anda mendefinisikan payload sebagai kamus, tuple atau daftar.

MultipartEncoderdapat digunakan baik untuk permintaan multi bagian dengan atau tanpa bidang unggahan yang sebenarnya. Itu harus ditugaskan ke dataparameter.

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

Jika Anda perlu mengirim beberapa bidang dengan nama yang sama, atau jika urutan bidang isian penting, maka tupel atau daftar dapat digunakan sebagai pengganti kamus:

multipart_data = MultipartEncoder(
    fields=(
            ('action', 'ingest'), 
            ('item', 'spam'),
            ('item', 'sausage'),
            ('item', 'eggs'),
           )
    )
ccpizza
sumber
Terima kasih untuk ini. Urutan kunci penting bagi saya dan ini banyak membantu.
Splendor
Luar biasa. Entah kenapa, api yang saya kerjakan membutuhkan 2 nilai berbeda untuk kunci yang sama. Ini luar biasa. Terima kasih.
ajon
@ccpizza, apa sebenarnya arti baris ini? > "('file.py', buka ('file.py', 'rb'), 'text / plain')". Itu tidak bekerja untuk saya :(
Denis Koreyba
@DenisKoreyba: ini adalah contoh bidang unggahan file yang mengasumsikan bahwa file yang bernama file.pyberada di folder yang sama dengan skrip Anda.
ccpizza
1
Anda bisa menggunakan Nonebukannya string kosong. Maka permintaan tidak akan menyertakan nama file sama sekali. Jadi, bukannya Content-Disposition: form-data; name="action"; filename=""itu akan terjadi Content-Disposition: form-data; name="action". Ini penting bagi saya bagi server untuk menerima bidang-bidang itu sebagai bidang formulir dan bukan sebagai file.
Mitar
9

Berikut ini cuplikan kode sederhana untuk mengunggah satu file dengan parameter tambahan menggunakan permintaan:

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)

Harap perhatikan bahwa Anda tidak perlu secara spesifik menentukan jenis konten apa pun.

CATATAN: Ingin mengomentari salah satu jawaban di atas tetapi tidak dapat karena reputasi rendah sehingga menyusun tanggapan baru di sini.

Jainik
sumber
4

Anda perlu menggunakan nameatribut file unggahan yang ada dalam HTML situs. Contoh:

autocomplete="off" name="image">

Kamu lihat name="image">? Anda dapat menemukannya di HTML situs untuk mengunggah file. Anda harus menggunakannya untuk mengunggah fileMultipart/form-data

naskah:

import requests

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

Di sini, di tempat gambar, tambahkan nama file unggahan dalam HTML

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

Jika unggahan perlu mengklik tombol untuk unggah, Anda dapat menggunakan seperti itu:

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

Kemudian mulailah permintaan itu

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

Dan selesai, file berhasil diunggah

Skiller Dz
sumber
3

Kirim kunci dan nilai multipart / formulir-data

perintah curl:

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

permintaan python - Permintaan POST yang lebih rumit :

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

Kirim file data multi-formulir / formulir

perintah 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

permintaan python - POST File yang Dienkripsi Banyak Bagian :

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

itu saja.

crifan
sumber
-1

Ini adalah potongan python yang Anda butuhkan untuk mengunggah satu file tunggal besar sebagai multipart formdata Dengan NodeJs Multer middleware berjalan di sisi server.

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

Untuk sisi server, silakan periksa dokumentasi multer di: https://github.com/expressjs/multer di sini field tunggal ('fieldName') digunakan untuk menerima satu file tunggal, seperti pada:

var upload = multer().single('fieldName');
vinaymk
sumber