331 votes

Comment envoyer un "multipart/form-data" avec des requêtes en python ?

Comment envoyer un multipart/form-data con requests en python ? Comment envoyer un fichier, je comprends, mais comment envoyer les données du formulaire par cette méthode ne peut pas comprendre.

0 votes

Votre question n'est pas vraiment claire. Que voulez-vous obtenir ? Souhaitez-vous envoyer des données "multipart/form-data" sans téléchargement de fichier dans le formulaire ?

4 votes

Le fait que files est utilisé pour faire les deux est une très mauvaise API. J'ai soulevé un problème intitulé Envoi de données multipart - nous avons besoin d'une meilleure API pour régler ce problème. Si vous êtes d'accord pour dire que l'utilisation files Le paramètre permettant d'envoyer des données en plusieurs parties est, au mieux, trompeur. Veuillez demander la modification de l'API dans la question ci-dessus.

0 votes

@PiotrDobrogost ce problème est clos. N'encouragez pas les gens à commenter les questions fermées, qu'elles soient pertinentes ou non.

295voto

Martijn Pieters Points 271458

Fondamentalement, si vous spécifiez un files (un dictionnaire), alors requests enverra un multipart/form-data POST au lieu d'un application/x-www-form-urlencoded POST. Vous n'êtes toutefois pas limité à l'utilisation de fichiers réels dans ce dictionnaire :

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

et httpbin.org vous permet de savoir avec quels en-têtes vous avez posté ; en response.json() nous avons :

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

Mieux encore, vous pouvez contrôler davantage le nom de fichier, le type de contenu et les en-têtes supplémentaires de chaque partie en utilisant un tuple au lieu d'une simple chaîne ou d'un objet octet. Le tuple est censé contenir entre 2 et 4 éléments : le nom de fichier, le contenu, éventuellement un type de contenu, et un dictionnaire facultatif d'en-têtes supplémentaires.

J'utiliserais la forme tuple avec None comme nom de fichier, de sorte que le filename="..." est supprimé de la demande pour ces parties :

>>> 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 peut également être une liste de tuples à deux valeurs, si vous avez besoin d'un ordre et/ou de plusieurs champs portant le même nom :

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

Si vous spécifiez à la fois files y data alors cela dépend de la valeur de data ce qui sera utilisé pour créer le corps du POST. Si data est une chaîne de caractères, elle seule sera utilisée ; sinon, les deux data y files sont utilisés, avec les éléments dans data en premier lieu.

Il existe également l'excellent requests-toolbelt qui comprend support multipart avancé . Il prend les définitions de champs dans le même format que la fonction files mais contrairement à requests il n'y a pas de paramètre de nom de fichier par défaut. En outre, il peut diffuser la requête à partir d'objets de type fichier ouvert, où requests construit d'abord le corps de la demande en mémoire :

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

Les champs suivent les mêmes conventions ; utilisez un tuple avec entre 2 et 4 éléments pour ajouter un nom de fichier, un mime-type partiel ou des en-têtes supplémentaires. Contrairement aux champs files aucune tentative n'est faite pour trouver un paramètre par défaut. filename si vous n'utilisez pas un tuple.

12 votes

Si files={} est utilisé, alors headers={'Content-Type':'blah blah'} ne doit pas être utilisé !

8 votes

@zaki : i multipart/form-data Content-Type doit inclure la valeur limite utilisée pour délimiter les pièces dans le corps du message. Le fait de ne pas définir la Content-Type garantit que requests le fixe à la valeur correcte.

3 votes

Remarque importante : la demande ne sera envoyée que sous forme de multipart/form-data si la valeur de files= est véridique, donc si vous avez besoin d'envoyer un multipart/form-data mais n'incluent aucun fichier, vous pouvez définir une valeur véridique mais sans signification telle que {'':''} et définir data= avec le corps de votre requête. Si vous faites cela, ne fournissez pas l'option Content-Type l'entête vous-même ; requests le réglera pour vous. Vous pouvez voir le contrôle de vérité ici : github.com/psf/requests/blob/

125voto

runejuhl Points 121

Les demandes ont changé depuis que certaines des réponses précédentes ont été écrites. Jetez un coup d'œil à ce numéro sur Github pour plus de détails et ce commentaire pour un exemple.

En bref, le files prend un dictionnaire dont la clé est le nom du champ de formulaire et dont la valeur est soit une chaîne de caractères, soit un tuple de 2, 3 ou 4 longueurs, comme décrit à la section POST un fichier Multipart-Encoded dans le démarrage rapide des requêtes :

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

Dans l'exemple ci-dessus, le tuple est composé comme suit :

(filename, data, content_type, headers)

Si la valeur n'est qu'une chaîne, le nom du fichier sera le même que celui de la clé, comme dans l'exemple suivant :

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

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

72c2b6f406cdabd578c5fd7598557c52

Si la valeur est un tuple et que la première entrée est None la propriété filename ne sera pas incluse :

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

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

72c2b6f406cdabd578c5fd7598557c52

2 votes

Et si vous deviez distinguer le name y filename mais aussi avoir plusieurs champs avec le même nom ?

1 votes

J'ai un problème similaire à celui de @Michael . Pouvez-vous jeter un coup d'œil à la question et suggérer quelque chose ? [lien]( stackoverflow.com/questions/30683352/ )

0 votes

Quelqu'un a-t-il résolu ce problème de champs multiples avec le même nom ?

114voto

ccpizza Points 2653

Vous devez utiliser le files pour envoyer une demande POST de formulaire multipartite. même lorsque vous n'avez pas besoin de télécharger de fichiers.

De l'original demande source :

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 partie pertinente est : file-tuple can be a :

  • 2-tuple ( nom de fichier, fichierobj )
  • 3-tuple ( filename, fileobj, content_type )
  • 4-tuple ( filename, fileobj, content_type, custom_headers ).

Ce qui n'est pas forcément évident, c'est que fileobj peut être soit un objet fichier réel lorsqu'il s'agit de fichiers, OU une chaîne de caractères lorsqu'il s'agit de champs de texte brut.

Sur la base de ce qui précède, la demande de formulaire multipartite la plus simple, qui comprend à la fois des fichiers à télécharger et des champs de formulaire, ressemblera à ceci :

import requests

multipart_form_data = {
    'upload': ('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)

Notez le None en tant que premier argument dans le tuple pour les champs de texte brut - il s'agit d'un caractère générique pour le champ nom de fichier qui n'est utilisé que pour les téléchargements de fichiers, mais pour les champs de texte passant par l'option None comme premier paramètre est nécessaire pour que les données soient soumises.

Plusieurs champs portant le même nom

Si vous devez afficher plusieurs champs portant le même nom, au lieu d'un dictionnaire, vous pouvez définir votre charge utile comme une liste (ou un tuple) de tuples :

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

Demandes de streaming API

Si l'API ci-dessus n'est pas assez pythique pour vous, alors envisagez d'utiliser demande une ceinture porte-outils ( pip install requests_toolbelt ) qui est une extension de la demandes principales qui prend en charge le téléchargement de fichiers en continu, ainsi que le module MultipartEncoder qui peut être utilisé à la place de files et qui vous permet également de définir la charge utile comme un dictionnaire, un tuple ou une liste.

MultipartEncoder peut être utilisé à la fois pour les demandes multipartites avec ou sans champs de téléchargement réels. Il doit être affecté à l'élément data paramètre.

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

Si vous devez envoyer plusieurs champs portant le même nom, ou si l'ordre des champs du formulaire est important, vous pouvez utiliser un tuple ou une liste au lieu d'un dictionnaire :

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

0 votes

Merci pour cela. L'ordre des touches était important pour moi et cela m'a beaucoup aidé.

0 votes

Incroyable. De manière inexplicable, une API avec laquelle je travaille requiert deux valeurs différentes pour la même clé. C'est incroyable. Merci.

0 votes

@ccpizza, que signifie réellement cette ligne ? > "('fichier.py', open('fichier.py', 'rb'), 'texte/plain')". Cela ne fonctionne pas pour moi :(

22voto

Jainik Points 1007

Voici un extrait de code simple pour télécharger un fichier unique avec des paramètres supplémentaires en utilisant des requêtes :

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)

Veuillez noter que vous n'avez pas besoin de spécifier explicitement un type de contenu.

REMARQUE : Je voulais commenter l'une des réponses ci-dessus, mais je n'ai pas pu le faire en raison de ma mauvaise réputation. J'ai donc rédigé une nouvelle réponse ici.

5 votes

Le moins verbeux et le plus facile à comprendre. De toute façon, si un fichier est open ed avec 'rb' option ?

1 votes

Oui, cela touche le cœur du problème : files y data tous deux sous forme de dicts

0 votes

Après les nombreuses réponses longues et compliquées ci-dessus, celle-ci va directement à l'essentiel et fonctionne !

7voto

Skiller Dz Points 510

Vous devez utiliser le name du fichier de téléchargement qui se trouve dans le HTML du site. Exemple :

autocomplete="off" name="image">

Vous voyez name="image"> ? Vous pouvez le trouver dans le HTML d'un site pour télécharger le fichier. Vous devez l'utiliser pour télécharger le fichier avec Multipart/form-data

script :

import requests

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

Ici, à la place de l'image, ajoutez le nom du fichier à télécharger en HTML

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

Si le téléchargement nécessite de cliquer sur le bouton de téléchargement, vous pouvez l'utiliser comme cela :

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

Ensuite, lancez la demande

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

Et voilà, le fichier a été téléchargé avec succès

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X