Cum să trimiteți un „multipart/form-data” cu cereri în python? (Programare, Python, Python 2.7, Date Multipartformă, Cereri Python)

agrynchuk a intrebat.

Cum să trimiteți un multipart/form-data cu cereri în python? Cum să trimiteți un fișier, înțeleg, dar cum să trimiteți datele formularului prin această metodă nu pot înțelege.

Comentarii

  • întrebarea dvs. nu este foarte clară. Ce doriți să obțineți? Doriți să trimiteți „multipart/form-data” fără o încărcare a fișierului în formular? –  > Por Hans Then.
  • Faptul că files este folosit pentru ambele scopuri este un API foarte prost. Am ridicat o problemă intitulată Trimiterea de date multipart – avem nevoie de un API mai bun pentru a rezolva acest lucru. Dacă sunteți de acord că utilizarea files pentru a trimite date multipart este înșelătoare în cel mai bun caz, vă rugăm să solicitați modificarea API în problema de mai sus. –  > Por Piotr Dobrogost.
  • @PiotrDobrogost această problemă este închisă. Nu încurajați oamenii să comenteze pe probleme închise, relevante sau nu. –  > Por Ian Stapleton Cordasco.
  • Nu mai contează, tocmai mi-am dat seama că comentariul tău a fost postat înainte de a fi închis. Urăsc modul în care StackOverflow nu păstrează lucrurile în ordine cronologică. –  > Por Ian Stapleton Cordasco.
  • verificați acest răspuns stackoverflow.com/a/64586578/8826047 Limita este importantă! –  > Por Sona Pochybova.
10 răspunsuri
Martijn Pieters

În principiu, dacă specificați un files (un dicționar), atunci requests va trimite un multipart/form-data POST în loc de un application/x-www-form-urlencoded POST. Cu toate acestea, nu sunteți limitat la utilizarea fișierelor reale din acel dicționar:

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

și httpbin.org vă permite să știți cu ce anteturi ați postat; în response.json() avem:

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

Și mai bine, puteți controla și mai mult numele fișierului, tipul de conținut și antetele suplimentare pentru fiecare parte, utilizând un tupluplu în loc de un singur șir de caractere sau un obiect bytes. Se așteaptă ca tuple-ul să conțină între 2 și 4 elemente: numele fișierului, conținutul, opțional un tip de conținut și un dicționar opțional de anteturi suplimentare.

Eu aș folosi forma de tuple cu None ca nume de fișier, astfel încât elementul filename="..." este eliminat din cerere pentru aceste părți:

>>> 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 poate fi, de asemenea, o listă de tuple cu două valori, dacă aveți nevoie de ordine și/sau de mai multe câmpuri cu același nume:

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

Dacă specificați atât files și data, atunci depinde de valoare din data ceea ce va fi utilizat pentru a crea corpul POST. Dacă data este un șir de caractere, se va utiliza doar acesta; în caz contrar, se vor utiliza atât data și files sunt utilizate, cu elementele din data enumerate mai întâi.

Există, de asemenea, excelenta funcție requests-toolbelt proiect, care include suport Multipart avansat. Acesta preia definiții de câmpuri în același format ca și files dar, spre deosebire de requests, nu setează în mod implicit un parametru de nume de fișier. În plus, poate transmite în flux cererea din obiecte de fișier deschise, unde requests va construi mai întâi corpul cererii în memorie:

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

Câmpurile urmează aceleași convenții; utilizați un tuplule cu între 2 și 4 elemente pentru a adăuga un nume de fișier, un tip mime parțial sau anteturi suplimentare. Spre deosebire de files nu se încearcă să se găsească un parametru implicit. filename în cazul în care nu se utilizează un tuple.

Comentarii

  • Dacă se utilizează files={}, atunci headers={‘Content-Type’:’blah blah’} nu trebuie să fie utilizat! –  > Por Zaki.
  • @zaki: într-adevăr, pentru că multipart/form-data Content-Type trebuie să fie să includă valoarea limită utilizată pentru a delimita părțile din corpul mesajului. Nu setarea valorii Content-Type asigură faptul că requests îl stabilește la valoarea corectă. –  > Por Martijn Pieters.
  • Notă importantă: cererea va fi trimisă doar ca multipart/form-data dacă valoarea lui files= este veridică, deci dacă trebuie să trimiteți o cerere de tip multipart/form-data dar nu includeți niciun fișier, puteți seta o valoare adevărată, dar fără semnificație, cum ar fi {'':''}, și să setați data= cu corpul cererii. Dacă faceți acest lucru, nu furnizați valoarea Content-Type de către dumneavoastră; requests îl va seta pentru dvs. Puteți vedea verificarea adevărului aici: github.com/psf/requests/blob/… –  > Por Daniel Situnayake.
  • @DanielSitunayake nu este nevoie de un astfel de hack. Pur și simplu puneți toate câmpurile în files dict, nu trebuie să fie fișiere (asigurați-vă doar că folosiți forma de tuple și setați numele fișierului la None). Mai bine, utilizați requests_toolbelt proiect. –  > Por Martijn Pieters.
  • Mulțumesc @MartijnPieters, trucul cu forma de tuple este grozav! O să încerc asta. –  > Por Daniel Situnayake.
runejuhl

De când au fost scrise răspunsurile anterioare, cererile s-au schimbat. Aruncați o privire la firul de discuții pe Github pentru mai multe detalii și acest comentariu pentru un exemplu.

Pe scurt, parametrul files primește un fișier dict cu cheia fiind numele câmpului de formular și valoarea fiind fie un șir de caractere, fie o tuple de 2, 3 sau 4 lungimi, așa cum este descris în secțiunea POSTAREA unui fișier cu codare multiparte din Ghidul rapid pentru cereri:

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

În cazul de mai sus, tuple-ul este compus după cum urmează:

(filename, data, content_type, headers)

Dacă valoarea este doar un șir de caractere, numele fișierului va fi același cu cel al cheii, ca în cazul de mai jos:

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

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

72c2b6f406cdabd578c5fd7598557c52

Dacă valoarea este un tuple și prima intrare este None proprietatea „filename” nu va fi inclusă:

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

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

72c2b6f406cdabd578c5fd7598557c52

Comentarii

  • Ce se întâmplă dacă trebuie să faceți distincția între name și filename dar aveți și mai multe câmpuri cu același nume? –  > Por Michael.
  • Am o problemă asemănătoare cu cea a lui @Michael . Poți să te uiți la întrebare și să-mi sugerezi ceva? [link]( stackoverflow.com/questions/30683352/…)  > Por Shaardool.
  • a rezolvat cineva această problemă de a avea mai multe câmpuri cu același nume? –  > Por user3131037.
  • Trucul de a trece en șir gol ca primă valoare a unui câmp files nu mai funcționează: trebuie să utilizați requests.post data pentru a trimite un parametru suplimentar care nu este un fișier multipart/form-data parametri –  > Por Lucas Cimon.
  • Transmiterea None în loc de un șir de caractere gol pare să funcționeze –  > Por Alexandre Blin.
ccpizza

Trebuie să utilizați files pentru a trimite o cerere POST de formular multipart chiar și atunci când nu trebuie să încărcați niciun fișier.

Din originalul requests sursă:

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.

Partea relevantă este: file-tuple can be a2-tuple, 3-tupleor a4-tuple.

Pe baza celor de mai sus, cea mai simplă cerere de formular multiparte care include atât fișiere de încărcat, cât și câmpuri de formular va arăta astfel:

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)

Rețineți că None ca prim argument în tuple pentru câmpurile de text simplu – acesta este un spațiu rezervat pentru câmpul filename care este utilizat doar pentru încărcarea fișierelor, dar pentru câmpurile de text care trec None ca prim parametru este necesar pentru ca datele să fie transmise.

Câmpuri multiple cu același nume

Dacă trebuie să trimiteți mai multe câmpuri cu același nume, în loc de un dicționar, puteți defini sarcina utilă ca o listă (sau un tuplule) de tupluri:

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 pentru solicitări în flux

Dacă API-ul de mai sus nu este suficient de pythonic pentru dumneavoastră, atunci luați în considerare utilizarea Centura de instrumente pentru cereri (pip install requests_toolbelt), care este o extensie a aplicației cereri de bază care oferă suport pentru încărcarea de fișiere în flux, precum și pentru MultipartEncoder care poate fi utilizat în loc de filesși care vă permite, de asemenea, să definiți sarcina utilă ca dicționar, tuple sau listă.

MultipartEncoder poate fi utilizat atât pentru cereri multipart cu sau fără câmpuri de încărcare propriu-zise. Acesta trebuie să fie atribuit la data parametru.

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

Dacă trebuie să trimiteți mai multe câmpuri cu același nume sau dacă ordinea câmpurilor din formular este importantă, în locul unui dicționar se poate utiliza un tuplule sau o listă:

multipart_data = MultipartEncoder(
    fields=(
            ('action', 'ingest'), 
            ('item', 'spam'),
            ('item', 'sausage'),
            ('item', 'eggs'),
           )
    )

Comentarii

  • Vă mulțumim pentru acest lucru. Ordinea cheilor era importantă pentru mine și acest lucru m-a ajutat foarte mult. –  > Por Splendor.
  • Uimitor. În mod inexplicabil, o api cu care lucrez necesită 2 valori diferite pentru aceeași cheie. Acest lucru este uimitor. Vă mulțumesc. –  > Por ajon.
  • @ccpizza, ce înseamnă de fapt această linie? > „(‘file.py’, open(‘file.py’, ‘rb’), ‘text/plain’)”. Nu funcționează pentru mine 🙁 – –  > Por Denis Koreyba.
  • @DenisKoreyba: acesta este un exemplu de câmp de încărcare a unui fișier care presupune că un fișier numit file.py se află în același folder cu scriptul tău. –  > Por ccpizza.
  • Puteți utiliza None în loc de șirul gol. În acest caz, cererile nu vor include deloc un nume de fișier. Deci, în loc de Content-Disposition: form-data; name="action"; filename="" va fi Content-Disposition: form-data; name="action". Acest lucru a fost esențial pentru mine pentru ca serverul să accepte aceste câmpuri ca fiind câmpuri de formular și nu ca fișiere. –  > Por Mitar.
Jainik

Iată fragmentul de cod simplu pentru a încărca un singur fișier cu parametri suplimentari folosind cereri:

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)

Vă rugăm să rețineți că nu este necesar să specificați în mod explicit niciun tip de conținut.

NOTĂ: Am vrut să comentez la unul dintre răspunsurile de mai sus, dar nu am putut din cauza reputației scăzute, așa că am redactat un nou răspuns aici.

Comentarii

  • Cel mai puțin verbos și cel mai ușor de înțeles. În orice caz, dacă un fișier ar trebui să fie opened cu'rb' opțiune? –  > Por ghchoi.
  • Da, acest lucru atinge miezul problemei: files și data ambele ca dicte –  > Por StephenBoesch.
Skiller Dz

Trebuie să folosiți name din fișierul de încărcare care se află în HTML-ul site-ului. Exemplu:

autocomplete="off" name="image">

Vedeți name="image">? Îl puteți găsi în HTML-ul unui site pentru încărcarea fișierului. Trebuie să îl utilizați pentru a încărca fișierul cu Multipart/form-data

script:

import requests

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

Aici, în locul imaginii, adăugați numele fișierului de încărcat în HTML.

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

În cazul în care pentru încărcare este necesar să faceți clic pe butonul de încărcare, puteți folosi așa:

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

Apoi începeți cererea

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

Și gata, fișierul a fost încărcat cu succes

Crifan

Trimiteți cheia și valoarea multipart/form-data

comanda curl:

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

python requests – Cereri POST mai complicate:

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

Trimiteți un fișier multipart/form-data

comanda curl:

curl -X POST http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' 
-F [email protected]/Users/xxx.txt

python requests – POST un fișier multipart-codat:

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

asta e tot.

Mohamed MosȜd
import requests
# assume sending two files
url = "put ur url here"
f1 = open("file 1 path", 'rb')
f2 = open("file 2 path", 'rb')
response = requests.post(url,files={"file1 name": f1, "file2 name":f2})
print(response)

RCross

Pentru a clarifica exemplele date mai sus,

„Trebuie să utilizați parametrul files pentru a trimite o cerere POST de formular multipart chiar și atunci când nu trebuie să încărcați niciun fișier.”

files={}

nu va funcționa, din păcate.

Va trebui să puneți niște valori fictive, de ex.

files={"foo": "bar"}

M-am confruntat cu acest lucru atunci când am încercat să încarc fișiere la API REST a Bitbucket și a trebuit să scriu această aberație pentru a evita temuta eroare „Unsupported Media Type”:

url = "https://my-bitbucket.com/rest/api/latest/projects/FOO/repos/bar/browse/foobar.txt"
payload = {'branch': 'master', 
           'content': 'text that will appear in my file',
           'message': 'uploading directly from python'}
files = {"foo": "bar"}
response = requests.put(url, data=payload, files=files)

:O=

Comentarii

  • Nu ați putea face requests.put(url, files=payload) –  > Por Nizam Mohamed.
vinaymk

Aici este fragmentul python de care aveți nevoie pentru a încărca un singur fișier mare ca formdata multipart. Cu NodeJs Multer middleware Multer care rulează pe partea serverului.

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

Pentru partea de server, vă rugăm să verificați documentația multer la: https://github.com/expressjs/multeraici câmpul single(‘fieldName’) este utilizat pentru a accepta un singur fișier, ca în:

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

qarly_blue

Încerc să trimit o solicitare către URL_server cu modulul request în python 3. Acest lucru funcționează pentru mine:

# -*- coding: utf-8 *-*
import json, requests

URL_SERVER_TO_POST_DATA = "URL_to_send_POST_request"
HEADERS = {"Content-Type" : "multipart/form-data;"}

def getPointsCC_Function():
  file_data = {
      'var1': (None, "valueOfYourVariable_1"),
      'var2': (None, "valueOfYourVariable_2")
  }

  try:
    resElastic = requests.post(URL_GET_BALANCE, files=file_data)
    res = resElastic.json()
  except Exception as e:
    print(e)

  print (json.dumps(res, indent=4, sort_keys=True))

getPointsCC_Function()

Unde:

  • URL_SERVER_TO_POST_DATA = Serverul unde vom trimite datele
  • HEADERS = Antetele trimise
  • file_data = Params trimise