¿Cómo enviar un “multipart / form-data” con las solicitudes en python?

¿Cómo enviar un multipart/form-data con peticiones en python? Cómo enviar un archivo, entiendo, pero cómo enviar los datos del formulario por este método no se puede entender.

Básicamente, si especifica un parámetro de files (un diccionario), las requests enviarán un POST multipart/form-data lugar de un POST de application/x-www-form-urlencoded . No estás limitado a usar archivos reales en ese diccionario, sin embargo:

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

y httpbin.org le permite saber con qué encabezados ha publicado; en response.json() tenemos:

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

Aún mejor, puede controlar aún más el nombre de archivo, el tipo de contenido y los encabezados adicionales para cada parte mediante el uso de una tupla en lugar de una cadena u objeto de bytes. Se espera que la tupla contenga entre 2 y 4 elementos; el nombre de archivo, el contenido, opcionalmente un tipo de contenido y un diccionario opcional de encabezados adicionales.

Usaría esa forma de tupla con None como nombre de archivo, de modo que el parámetro filename="..." se elimine de la solicitud de esas partes:

 >>> 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 también pueden ser una lista de tuplas de dos valores, si necesita ordenar y / o múltiples campos con el mismo nombre:

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

Si especifica los files y los data , entonces depende del valor de los data que se utilizarán para crear el cuerpo POST. Si los data son una cadena, solo se utilizará; de lo contrario files se utilizan tanto los data como los files , con los elementos en los data listados primero.

También está el excelente proyecto de requests-toolbelt , que incluye soporte avanzado de requests-toolbelt . Toma las definiciones de campo en el mismo formato que el parámetro de files , pero a diferencia de las requests , no establece un parámetro de nombre de archivo. Además, puede transmitir la solicitud desde los objetos de archivo abierto, donde las requests primero construirán el cuerpo de la solicitud en la memoria.

Desde que se escribieron las respuestas anteriores, las solicitudes han cambiado. Echa un vistazo al hilo de error en Github para obtener más detalles y este comentario para un ejemplo.

En resumen, el parámetro files toma un dict La clave es el nombre del campo de formulario y el valor es una cadena o una tupla de 2, 3 o 4 longitudes, como se describe en la sección POSTE de un archivo codificado en varias partes en la solicita inicio rápido:

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

En lo anterior, la tupla se compone de la siguiente manera:

 (filename, data, content_type, headers) 

Si el valor es solo una cadena, el nombre de archivo será el mismo que la clave, como en el siguiente:

 >>> files = {'obvius_session_id': '72c2b6f406cdabd578c5fd7598557c52'} Content-Disposition: form-data; name="obvius_session_id"; filename="obvius_session_id" Content-Type: application/octet-stream 72c2b6f406cdabd578c5fd7598557c52 

Si el valor es una tupla y la primera entrada es None la propiedad de nombre de archivo no se incluirá:

 >>> files = {'obvius_session_id': (None, '72c2b6f406cdabd578c5fd7598557c52')} Content-Disposition: form-data; name="obvius_session_id" Content-Type: application/octet-stream 72c2b6f406cdabd578c5fd7598557c52 

Debe utilizar el parámetro de files para enviar una solicitud POST de formulario de varias partes, incluso cuando no necesita cargar ningún archivo.

De la fuente de solicitudes original:

 def request(method, url, **kwargs): """Constructs and sends a :class:`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 relevante es: file-tuple can be a 2-tuple , 3-tuple or a 4-tuple .

En base a lo anterior, la solicitud de formulario multiparte más simple que incluye tanto archivos para cargar como campos de formulario se verá así:

 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) 

Observe el valor None como el primer argumento en la tupla para los campos de texto simple: este es un marcador de posición para el campo de nombre de archivo que solo se usa para la carga de archivos, pero para los campos de texto que pasan None como primer parámetro es necesario para que los datos ser presentado


Si esta API no es lo suficientemente pythonic para usted, o si necesita publicar varios campos con el mismo nombre, entonces considere usar el toolbelt de solicitudes ( pip install requests_toolbelt request_toolbelt ), que es una extensión del módulo de solicitudes principales que brinda soporte para la transmisión de archivos como así como el MultipartEncoder que se puede usar en lugar de los files , y que acepta parámetros como diccionarios y tuplas.

MultipartEncoder se puede usar para solicitudes multiparte con o sin campos de carga reales. Debe ser asignado al parámetro de 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 necesita enviar varios campos con el mismo nombre, o si el orden de los campos de formulario es importante, se puede usar una tupla o una lista en lugar de un diccionario, es decir:

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

Debe usar el atributo de name del archivo de carga que se encuentra en el HTML del sitio. Ejemplo:

 autocomplete="off" name="image"> 

¿Ves name="image"> ? Puede encontrarlo en el HTML de un sitio para cargar el archivo. Necesitas usarlo para cargar el archivo con Multipart/form-data

guión:

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

Aquí, en el lugar de la imagen, agregue el nombre del archivo cargado en HTML

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

Si la carga requiere hacer clic en el botón para cargar, puede usar así:

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

Entonces comience la solicitud

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

Y listo, archivo subido exitosamente.

Aquí está el fragmento de código simple para cargar un solo archivo con parámetros adicionales mediante solicitudes:

 url = 'https://' 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) 

Tenga en cuenta que no necesita especificar explícitamente ningún tipo de contenido.

NOTA: Quería comentar una de las respuestas anteriores, pero no pudo debido a la baja reputación, por lo que redacté una nueva respuesta aquí.

Aquí está el fragmento de código de Python que necesita para cargar un solo archivo grande como datos de formulario de varias partes. Con el middleware NodeJs Multer ejecutándose en el lado del servidor.

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

Para el lado del servidor, consulte la documentación del multer en: https://github.com/expressjs/multer aquí. El campo single (‘fieldName’) se usa para aceptar un solo archivo, como en:

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