2012-09-12 13 views
104

¿Cómo enviar un multipart/form-data con solicitudes en python? Cómo entiendo cómo enviar un archivo, pero no puedo entender cómo enviar los datos del formulario mediante este método.¿Cómo enviar un "multipart/form-data" con solicitudes en python?

+0

tu pregunta no es muy clara. ¿Qué quieres lograr? ¿Desea enviar "multipart/form-data" sin una carga de archivo en el formulario? –

+2

El hecho de que el parámetro 'files' se use para hacer ambas cosas es una API muy mala. Planteé el tema titulado [Envío de datos de varias partes: necesitamos una mejor API] (https://github.com/kennethreitz/requests/issues/935) para solucionarlo. Si acepta que el uso del parámetro 'files' para enviar datos de mulitpart es engañoso en el mejor de los casos, solicite cambiar la API en el problema anterior. –

+0

@PiotrDobrogost ese problema está cerrado. No anime a las personas a comentar sobre cuestiones cerradas, relevantes o no. –

Respuesta

81

Básicamente, si especifica un parámetro files (un diccionario), entonces requests enviará un POST multipart/form-data en lugar de un POST application/x-www-form-urlencoded. No está limitado a la utilización de los 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 lo que los ficheros de encabezado informado con; en response.json() tenemos:

>>> from pprint import pprint 
>>> pprint(response.json()['headers']) 
{u'Accept': u'*/*', 
u'Accept-Encoding': u'gzip, deflate, compress', 
u'Connection': u'close', 
u'Content-Length': u'141', 
u'Content-Type': u'multipart/form-data; boundary=33b4531a79be4b278de5f5688fab7701', 
u'Host': u'httpbin.org', 
u'User-Agent': u'python-requests/2.2.1 CPython/2.7.6 Darwin/13.2.0', 
u'X-Request-Id': u'eaf6baf8-fc3d-456b-b17d-e8219ccef1b1'} 

files también puede ser una lista de tuplas de dos valores, si es necesario ordenar y/o múltiples campos con el mismo nombre:

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

Si especifica files y data, entonces depende del valor de data lo que se usará para crear el cuerpo POST. Si data es una cadena, solo se usará; de lo contrario, se usan data y files, con los elementos en data listados primero.

+4

Esto codificará cualquier cosa enviada a 'archivos' como un parámetro de archivo real en la codificación multiparte. Esto no creará una forma estricta sino un formulario con todos los parámetros de archivo. Ver [esto] (https://github.com/kennethreitz/requests/issues/1081) para referencia. –

+0

@ sigmavirus24: la API de solicitudes ha evolucionado desde que publiqué esto; déjame investigar si esto necesita una actualización ahora. En cualquier caso, esta esquina de la API [ha estado necesitando una revisión por un tiempo ahora] (https://github.com/kennethreitz/requests/issues/935). –

+0

disculpas. StackOverflow pone esto cerca de la parte superior y olvido las preguntas de reorganización y tengo que mirar las fechas respondidas/solicitadas. –

68

Como se escribieron las respuestas anteriores, las solicitudes han cambiado. Eche un vistazo a bug thread at Github para más detalles y this comment para un ejemplo.

En resumen, el parámetro de archivos toma dict con la clave que 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 POST a Multipart-Encoded File en las solicitudes 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 sólo una cadena, el nombre del archivo será la misma 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 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 
+1

¿Qué sucede si necesita distinguir 'nombre' y' nombre_archivo' pero también tiene varios campos con el mismo nombre? – Michael

+1

Tengo un problema similar a @Michael. ¿Puedes echar un vistazo a la pregunta y sugerir algo? [link] (http://stackoverflow.com/questions/30683352/how-to-upload-multipart-encode-file-and-form-data-as-a-payload) – Shaardool

+0

¿Alguien resolvió este problema al tener múltiples campos? con el mismo nombre? – user3131037

27

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

Desde el original requests fuente:

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 solicitud impreso de varias copias más simple que incluye tanto los datos para cargar y campos de formulario se vería así:

multipart_form_data = { 
    'file1': open('myfile.zip', 'rb'), 
    'file2': ('custom_file_name.zip', open('myfile.zip', 'rb')), 
    'action': ('', 'store'), 
    'path': ('', '/path1') 
} 

response = requests.post('https://httpbin.org/post', files=multipart_form_data) 

print(response.content) 

Nota la cadena vacía como el primer elemento en la tupla para campos de texto plano: este es un marcador de posición para el campo de nombre de archivo que solo se usa para cargar archivos, pero para el campo de texto, el marcador de posición vacío debe estar presente en orden para los datos que se enviarán


Si esta API no es suficiente Pythonic para usted, o si usted necesita para enviar múltiples campos con el mismo nombre, y luego considerar el uso de requests toolbelt ( pip install requests_toolbelt) que es una extensión del módulo core requests que proporciona soporte para archivos cargar la transmisión, así como MultipartEncoder que se puede utilizar en lugar de files, y que acepta parámetros como diccionarios y tuplas.

MultipartEncoder puede utilizarse tanto para solicitudes de varias partes con o sin campos de carga reales. Debe asignarse al parámetro data.

import requests 
from requests_toolbelt.multipart.encoder import MultipartEncoder 

multipart_data = MultipartEncoder(
    fields={ 
      # a file upload field 
      'file': ('file.py', open('file.py', '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 del formulario es importante, entonces una tupla o una lista se puede utilizar en lugar de un diccionario, es decir:

multipart_data = MultipartEncoder(
    fields=(
      ('action', 'store'), 
      ('path', '/path1'), 
      ('path', '/path2'), 
      ('path', '/path3'), 
      ) 
    ) 
+0

Gracias por esto. El orden de las llaves fue importante para mí y esto ayudó mucho. – Splendor

+0

Increíble. Inexplicablemente, una API con la que estoy trabajando requiere 2 valores diferentes para la misma clave. Esto es increíble. Gracias. – ajon

+0

@ccpizza, ¿qué significa realmente esta línea? > "('file.py', open ('file.py', 'rb'), 'text/plain')". No funciona para mí :( –

Cuestiones relacionadas