web-dev-qa-db-fra.com

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

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

148
agrynchuk

En gros, si vous spécifiez un paramètre files (un dictionnaire), alors requests enverra un multipart/form-data POST au lieu d'un application/x-www-form-urlencoded POST. Vous n'êtes pas limité à utiliser des fichiers réels dans ce dictionnaire, cependant:

>>> 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 des en-têtes supplémentaires pour chaque partie en utilisant un tuple au lieu d'une seule chaîne ou d'un seul octet. Le tuple devrait contenir entre 2 et 4 éléments; le nom de fichier, le contenu, éventuellement un type de contenu et un dictionnaire optionnel d'en-têtes supplémentaires.

J'utiliserais le formulaire Tuple avec None comme nom de fichier, de sorte que le paramètre filename="..." soit supprimé de la demande de 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 aussi être une liste de n-uplets à deux valeurs, si vous avez besoin de commander et/ou plusieurs champs du même nom:

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

Si vous spécifiez à la fois files et data, tout dépend du value de data qui sera utilisé pour créer le corps POST. Si data est une chaîne, seule celle-ci sera utilisée; sinon, data et files sont utilisés, les éléments de data étant répertoriés en premier.

Il y a aussi l'excellent projet requests-toolbelt, qui inclut support multipart avancé . Il prend les définitions de champ dans le même format que le paramètre files, mais contrairement à requests, il ne définit par défaut aucun paramètre de nom de fichier. En outre, il peut diffuser la demande à partir d'objets de fichier ouverts, où requests construira d'abord le corps de la demande en mémoire.

97
Martijn Pieters

Depuis que les réponses précédentes ont été écrites, les demandes ont changé. Jetez un coup d'œil au thread bug de Github pour plus de détails et à this comment pour un exemple.

En bref, le paramètre files prend une dict, la clé étant le nom du champ de formulaire et la valeur une chaîne ou un tuple de 2, 3 ou 4 longueurs, comme décrit dans la section POST Fichier codé en plusieurs parties dans le démarrage rapide des demandes:

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

Dans ce qui précède, le tuple est composé comme suit:

(filename, data, content_type, headers)

Si la valeur est juste une chaîne, le nom du fichier sera le même que celui de la clé, comme dans ce qui suit:

>>> 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
92
runejuhl

Vous devez utiliser le paramètre files pour envoyer une demande de formulaire POST en plusieurs parties voire lorsque vous n'avez pas besoin de télécharger de fichier. 

De l'original requêtes 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 la suivante: file-Tuple can be a2-Tuple, 3-Tupleor a4-Tuple.

Sur la base de ce qui précède, la demande de formulaire en plusieurs parties la plus simple, qui inclut les fichiers à télécharger et les champs de formulaire, se présente comme suit:

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)

Notez que None est le premier argument du nuplet pour les champs de texte brut - il s'agit d'un espace réservé pour le champ nom de fichier utilisé uniquement pour les téléchargements de fichiers, mais pour les champs de texte avec None comme premier paramètre requis données à soumettre.


Si cette API ne vous suffit pas, ou si vous devez publier plusieurs champs de même nom, envisagez d’utiliser request toolbelt (pip install requests_toolbelt), qui est une extension de la core). requêtes module qui prend en charge le streaming de téléchargement de fichier ainsi que le MultipartEncoder qui peut être utilisé à la place de files et qui accepte les paramètres à la fois comme dictionnaires et tuples.

MultipartEncoder peut être utilisé à la fois pour des demandes en plusieurs parties avec ou sans champs de téléchargement réels. Il doit être affecté au paramètre 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})

Si vous devez envoyer plusieurs champs avec le même nom, ou si l'ordre des champs de formulaire est important, vous pouvez utiliser un tuple ou une liste à la place d'un dictionnaire, à savoir:

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

Vous devez utiliser l'attribut name du fichier de téléchargement contenu dans le code HTML du site. Exemple:

autocomplete="off" name="image">

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

scénario:

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 de téléchargement en HTML

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

Si le téléchargement nécessite de cliquer sur le bouton pour le télécharger, vous pouvez utiliser comme suit:

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

Puis lancez la demande

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

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

4
Skiller Dz

Voici l'extrait de code Python dont vous avez besoin pour télécharger un seul grand fichier en tant que données de formulaire multiparties. Avec le middleware NodeJs Multer fonctionnant côté serveur. 

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

Pour le côté serveur, veuillez consulter la documentation de multer à l’adresse: https://github.com/expressjs/multer Ici, le champ single ('fieldName') est utilisé pour accepter un seul fichier, comme dans:

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

Voici l'extrait de code simple permettant de télécharger un seul fichier avec des paramètres supplémentaires à l'aide de 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.

NOTE: Voulait commenter une des réponses ci-dessus mais ne pouvait pas en raison de sa faible réputation, a rédigé une nouvelle réponse ici.

0
Jainik