Come inviare un "multipart / form-data" con richieste in Python?


214

Come inviare un multipart/form-datacon richieste in Python? Come inviare un file, ho capito, ma come inviare i dati del modulo con questo metodo non riesco a capire.


la tua domanda non è molto chiara. Cosa vuoi ottenere? Desideri inviare "multipart / form-data" senza caricare un file nel modulo?
Hans, quindi,

4
Il fatto che il filesparametro sia usato per fare entrambe le cose è un'API molto negativa. Ho sollevato un problema intitolato Invio di dati multipart: abbiamo bisogno di API migliori per risolvere questo problema. Se si accetta che l'utilizzo del filesparametro per inviare dati mulitpart è fuorviante nella migliore delle ipotesi, si prega di chiedere di modificare l'API nel problema precedente.
Piotr Dobrogost,

@PiotrDobrogost il problema è stato chiuso. Non incoraggiare le persone a commentare questioni chiuse, pertinenti o meno.
Ian Stapleton Cordasco,

1
Non importa, ho appena realizzato che il tuo commento è stato pubblicato prima che fosse chiuso. Odio come StackOverflow non mantenga le cose in ordine cronologico.
Ian Stapleton Cordasco,

Risposte:


168

Fondamentalmente, se si specifica un filesparametro (un dizionario), requestsverrà inviato un multipart/form-dataPOST anziché un application/x-www-form-urlencodedPOST. Non sei limitato all'utilizzo dei file effettivi in ​​quel dizionario, tuttavia:

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

e httpbin.org ti consente di sapere con quali intestazioni hai pubblicato; in response.json()abbiamo:

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

Meglio ancora, puoi controllare ulteriormente il nome del file, il tipo di contenuto e le intestazioni aggiuntive per ogni parte usando una tupla invece di una singola stringa o oggetto byte. La tupla dovrebbe contenere tra 2 e 4 elementi; il nome file, il contenuto, facoltativamente un tipo di contenuto e un dizionario opzionale di ulteriori intestazioni.

Userei il modulo tupla con Nonecome nome file, in modo che il filename="..."parametro venga eliminato dalla richiesta per quelle parti:

>>> 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 può anche essere un elenco di tuple a due valori, se hai bisogno di ordinare e / o più campi con lo stesso nome:

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

Se si specificano entrambi filese data, dipende dal valore di dataciò che verrà utilizzato per creare il corpo POST. Se dataè una stringa, verrà utilizzata solo questa; altrimenti entrambi datae filesvengono utilizzati, con gli elementi dataelencati per primi.

C'è anche l'eccellente requests-toolbeltprogetto, che include il supporto avanzato Multipart . Accetta le definizioni dei campi nello stesso formato del filesparametro, ma diversamente requests, per impostazione predefinita non imposta un parametro del nome file. Inoltre, può eseguire lo streaming della richiesta da oggetti file aperti, dove requestscostruirà prima il corpo della richiesta in memoria:

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

I campi seguono le stesse convenzioni; usa una tupla con un numero di elementi compreso tra 2 e 4 per aggiungere un nome file, parte di tipo mime o intestazioni extra. A differenza del filesparametro, non si tenta di trovare un filenamevalore predefinito se non si utilizza una tupla.


3
Se si usano files = {} allora headers = {'Content-Type': 'blah blah'} non deve essere usato!
zaki,

5
@zaki: infatti, perché il multipart/form-dataContent-Type deve includere il valore limite utilizzato per delineare le parti nel corpo del post. La mancata impostazione Content-Typedell'intestazione garantisce che la requestsimposta sul valore corretto.
Martijn Pieters

Nota importante: la richiesta verrà inviata solo come multipart/form-datase il valore di files=è veritiero, quindi se è necessario inviare una multipart/form-datarichiesta ma non si stanno includendo alcun file, è possibile impostare un valore veritiero ma insignificante come {'':''}, e impostato data=con il corpo della richiesta. Se lo stai facendo, non fornire l' Content-Typeintestazione tu stesso; requestslo preparerà per te. Puoi vedere la verità qui: github.com/psf/requests/blob/…
Daniel Situnayake

@DanielSitunayake non è necessario un tale hack. Inserisci tutti i campi nel filesdict, non devono essere file (assicurati di utilizzare il modulo tupla e impostare il nome file su None). Meglio ancora, usa il requests_toolbeltprogetto.
Martijn Pieters

Grazie @MartijnPieters, il trucco con la forma tupla è fantastico! Ci proverò.
Daniel Situnayake,

107

Da quando sono state scritte le risposte precedenti, le richieste sono cambiate. Dai un'occhiata al thread di bug su Github per maggiori dettagli e questo commento per un esempio.

In breve, il parametro files accetta a dictcon la chiave come nome del campo modulo e il valore può essere una stringa o una tupla di 2, 3 o 4 lunghezze, come descritto nella sezione POST un file con codifica multipart nelle richieste avvio rapido:

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

In quanto sopra, la tupla è composta come segue:

(filename, data, content_type, headers)

Se il valore è solo una stringa, il nome file sarà lo stesso della chiave, come indicato di seguito:

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

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

72c2b6f406cdabd578c5fd7598557c52

Se il valore è una tupla e la prima voce è Nonela proprietà del nome file non verrà inclusa:

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

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

72c2b6f406cdabd578c5fd7598557c52

2
Cosa succede se è necessario distinguere namee filenamema avere anche più campi con lo stesso nome?
Michael,

1
Ho un problema simile a @Michael. Puoi dare un'occhiata alla domanda e suggerire qualcosa? [link] ( stackoverflow.com/questions/30683352/... )
Shaardool

qualcuno ha risolto questo problema con più campi con lo stesso nome?
user3131037

1
Il trucco per passare en stringa vuota come primo valore di una filestupla non funziona più: è necessario utilizzare requests.post datail parametro invece di inviare non-lima addizionale multipart/form-dataparametri
Lucas Cimon

1
Il passaggio Noneinvece di una stringa vuota sembra funzionare
Alexandre Blin,

74

È necessario utilizzare il filesparametro per inviare una richiesta POST di modulo multipart anche quando non è necessario caricare alcun file.

Dalla fonte delle richieste originali :

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.

La parte rilevante è: file-tuple can be a2-tuple, .3-tupleor a4-tuple

Sulla base di quanto sopra, la richiesta di modulo multipart più semplice che include sia i file da caricare sia i campi del modulo sarà simile al seguente:

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)

Nota Nonecome primo argomento nella tupla per i campi di testo semplice: si tratta di un segnaposto per il campo nome file che viene utilizzato solo per i caricamenti di file, ma per i campi di testo che passano Nonecome primo parametro è necessario per l'invio dei dati .

Più campi con lo stesso nome

Se devi pubblicare più campi con lo stesso nome, invece di un dizionario puoi definire il tuo payload come un elenco (o una tupla) di 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 richieste di streaming

Se l'API di cui sopra non è abbastanza pythonic per te, allora considera l'utilizzo di request toolbelt ( pip install requests_toolbelt) che è un'estensione del modulo di richieste core che fornisce supporto per lo streaming di upload di file e MultipartEncoder che può essere utilizzato al posto di files, e che consente anche si definisce il payload come dizionario, tupla o elenco.

MultipartEncoderpuò essere utilizzato sia per richieste multipart con o senza campi di upload effettivi. Deve essere assegnato al dataparametro.

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

Se è necessario inviare più campi con lo stesso nome o se l'ordine dei campi modulo è importante, è possibile utilizzare una tupla o un elenco anziché un dizionario:

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

Grazie per questo. L'ordine delle chiavi era importante per me e questo mi ha aiutato molto.
Splendore

Sorprendente. Inspiegabilmente, un API con cui sto lavorando richiede 2 valori diversi per la stessa chiave. Questo è fantastico Grazie.
ajon,

@ccpizza, cosa significa in realtà questa linea? > "('file.py', open ('file.py', 'rb'), 'text / plain')". Per me non funziona :(
Denis Koreyba,

@DenisKoreyba: questo è un esempio di un campo di caricamento di file che presuppone che il file chiamato file.pysi trovi nella stessa cartella dello script.
ccpizza,

1
È possibile utilizzare Noneinvece di stringa vuota. Quindi le richieste non includeranno affatto un nome file. Quindi invece Content-Disposition: form-data; name="action"; filename=""lo sarà Content-Disposition: form-data; name="action". Questo è stato fondamentale per me affinché il server accettasse quei campi come campi modulo e non come file.
Mitar,

9

Ecco il semplice frammento di codice per caricare un singolo file con parametri aggiuntivi utilizzando le richieste:

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)

Si noti che non è necessario specificare esplicitamente alcun tipo di contenuto.

NOTA: Volevo commentare una delle risposte di cui sopra ma non potevo a causa della bassa reputazione, quindi ho redatto una nuova risposta qui.


4

Devi usare l' nameattributo del file di upload che si trova nell'HTML del sito. Esempio:

autocomplete="off" name="image">

Vedi name="image"> ? Puoi trovarlo nell'HTML di un sito per caricare il file. Devi usarlo per caricare il file conMultipart/form-data

script:

import requests

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

Qui, al posto dell'immagine, aggiungi il nome del file di caricamento in HTML

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

Se il caricamento richiede di fare clic sul pulsante per il caricamento, è possibile utilizzare in questo modo:

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

Quindi avviare la richiesta

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

E fatto, il file è stato caricato con successo


3

Invia chiave e valore multipart / form-data

comando arricciatura:

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

richieste Python - Richieste POST più complicate :

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

Invia file multipart / form-data

comando arricciatura:

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

richieste python - POST un file con codifica multipart :

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

È tutto.


-1

Ecco lo snippet di Python necessario per caricare un singolo file di grandi dimensioni come formdata multipart. Con il middleware Multer NodeJs in esecuzione sul lato 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)

Per il lato server, consultare la documentazione della multer su: https://github.com/expressjs/multer qui il campo single ('fieldName') viene utilizzato per accettare un singolo file, come in:

var upload = multer().single('fieldName');
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.