Enviar archivo usando POST desde un script Python

Respuestas:

214

De: https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file

Requests hace que sea muy sencillo cargar archivos codificados con varias partes:

with open('report.xls', 'rb') as f:
    r = requests.post('http://httpbin.org/post', files={'report.xls': f})

Eso es. No estoy bromeando, esta es una línea de código. El archivo fue enviado. Vamos a revisar:

>>> r.text
{
  "origin": "179.13.100.4",
  "files": {
    "report.xls": "<censored...binary...data>"
  },
  "form": {},
  "url": "http://httpbin.org/post",
  "args": {},
  "headers": {
    "Content-Length": "3196",
    "Accept-Encoding": "identity, deflate, compress, gzip",
    "Accept": "*/*",
    "User-Agent": "python-requests/0.8.0",
    "Host": "httpbin.org:80",
    "Content-Type": "multipart/form-data; boundary=127.0.0.1.502.21746.1321131593.786.1"
  },
  "data": ""
}
Piotr Dobrogost
fuente
2
Estoy intentando lo mismo y funciona bien si el tamaño del archivo es inferior a ~ 1.5 MB. de lo contrario, arroja un error ... por favor, mire aquí .
Niks Jain
1
lo que estoy tratando de hacer es iniciar sesión en algún sitio usando una solicitud que hice con éxito pero ahora quiero subir un video después de iniciar sesión y el formulario tiene diferentes campos para completar antes de enviarlo. Entonces, ¿cómo debería pasar esos valores como la descripción vídeos, vídeos título etc
TaraGurung
15
Probablemente quieras hacer en su with open('report.xls', 'rb') as f: r = requests.post('http://httpbin.org/post', files={'report.xls': f})lugar, por lo que cierra el archivo nuevamente después de abrirlo.
Hjulle
3
¿Eh? ¿Desde cuándo enviar solicitudes es tan simple?
palsch
1
Esta respuesta debe actualizarse para incluir la sugerencia de Hjulle de usar el administrador de contexto para garantizar que el archivo esté cerrado.
bmoran
28

Si. Usaría el urllib2módulo y codificaría usando el multipart/form-datatipo de contenido. Aquí hay un código de muestra para comenzar: es un poco más que solo cargar archivos, pero debería poder leerlo y ver cómo funciona:

user_agent = "image uploader"
default_message = "Image $current of $total"

import logging
import os
from os.path import abspath, isabs, isdir, isfile, join
import random
import string
import sys
import mimetypes
import urllib2
import httplib
import time
import re

def random_string (length):
    return ''.join (random.choice (string.letters) for ii in range (length + 1))

def encode_multipart_data (data, files):
    boundary = random_string (30)

    def get_content_type (filename):
        return mimetypes.guess_type (filename)[0] or 'application/octet-stream'

    def encode_field (field_name):
        return ('--' + boundary,
                'Content-Disposition: form-data; name="%s"' % field_name,
                '', str (data [field_name]))

    def encode_file (field_name):
        filename = files [field_name]
        return ('--' + boundary,
                'Content-Disposition: form-data; name="%s"; filename="%s"' % (field_name, filename),
                'Content-Type: %s' % get_content_type(filename),
                '', open (filename, 'rb').read ())

    lines = []
    for name in data:
        lines.extend (encode_field (name))
    for name in files:
        lines.extend (encode_file (name))
    lines.extend (('--%s--' % boundary, ''))
    body = '\r\n'.join (lines)

    headers = {'content-type': 'multipart/form-data; boundary=' + boundary,
               'content-length': str (len (body))}

    return body, headers

def send_post (url, data, files):
    req = urllib2.Request (url)
    connection = httplib.HTTPConnection (req.get_host ())
    connection.request ('POST', req.get_selector (),
                        *encode_multipart_data (data, files))
    response = connection.getresponse ()
    logging.debug ('response = %s', response.read ())
    logging.debug ('Code: %s %s', response.status, response.reason)

def make_upload_file (server, thread, delay = 15, message = None,
                      username = None, email = None, password = None):

    delay = max (int (delay or '0'), 15)

    def upload_file (path, current, total):
        assert isabs (path)
        assert isfile (path)

        logging.debug ('Uploading %r to %r', path, server)
        message_template = string.Template (message or default_message)

        data = {'MAX_FILE_SIZE': '3145728',
                'sub': '',
                'mode': 'regist',
                'com': message_template.safe_substitute (current = current, total = total),
                'resto': thread,
                'name': username or '',
                'email': email or '',
                'pwd': password or random_string (20),}
        files = {'upfile': path}

        send_post (server, data, files)

        logging.info ('Uploaded %r', path)
        rand_delay = random.randint (delay, delay + 5)
        logging.debug ('Sleeping for %.2f seconds------------------------------\n\n', rand_delay)
        time.sleep (rand_delay)

    return upload_file

def upload_directory (path, upload_file):
    assert isabs (path)
    assert isdir (path)

    matching_filenames = []
    file_matcher = re.compile (r'\.(?:jpe?g|gif|png)$', re.IGNORECASE)

    for dirpath, dirnames, filenames in os.walk (path):
        for name in filenames:
            file_path = join (dirpath, name)
            logging.debug ('Testing file_path %r', file_path)
            if file_matcher.search (file_path):
                matching_filenames.append (file_path)
            else:
                logging.info ('Ignoring non-image file %r', path)

    total_count = len (matching_filenames)
    for index, file_path in enumerate (matching_filenames):
        upload_file (file_path, index + 1, total_count)

def run_upload (options, paths):
    upload_file = make_upload_file (**options)

    for arg in paths:
        path = abspath (arg)
        if isdir (path):
            upload_directory (path, upload_file)
        elif isfile (path):
            upload_file (path)
        else:
            logging.error ('No such path: %r' % path)

    logging.info ('Done!')
John Millikin
fuente
1
En python 2.6.6 recibí un error en el análisis de límites de varias partes al usar este código en Windows. Tuve que cambiar de string.letters a string.ascii_letters como se discutió en stackoverflow.com/questions/2823316/… para que esto funcione. El requisito sobre el límite se discute aquí: stackoverflow.com/questions/147451/…
amit
llamar a run_upload ({'servidor': '', 'hilo': ''}, caminos = ['/ ruta / a / archivo.txt']) causa un error en esta línea: upload_file (ruta) porque "cargar archivo" requiere 3 parámetros, así que lo reemplaza con esta línea upload_file (ruta, 1, 1)
Radian
4

Lo único que le impide usar urlopen directamente en un objeto de archivo es el hecho de que el objeto de archivo incorporado carece de una definición de len . Una manera simple es crear una subclase, que proporciona urlopen con el archivo correcto. También he modificado el encabezado Content-Type en el archivo a continuación.

import os
import urllib2
class EnhancedFile(file):
    def __init__(self, *args, **keyws):
        file.__init__(self, *args, **keyws)

    def __len__(self):
        return int(os.fstat(self.fileno())[6])

theFile = EnhancedFile('a.xml', 'r')
theUrl = "http://example.com/abcde"
theHeaders= {'Content-Type': 'text/xml'}

theRequest = urllib2.Request(theUrl, theFile, theHeaders)

response = urllib2.urlopen(theRequest)

theFile.close()


for line in response:
    print line
ilmarinen
fuente
@robert Pruebo tu código en Python2.7 pero no funciona. urlopen (Request (theUrl, theFile, ...)) simplemente codifica el contenido del archivo como si fuera una publicación normal, pero no puede especificar el campo de formulario correcto. Incluso intento la variante urlopen (theUrl, urlencode ({'serveride_field_name': EnhancedFile ('my_file.txt')})), carga un archivo pero (¡por supuesto!) Con contenido incorrecto como <abrir el archivo 'my_file.txt', modo 'r' en 0x00D6B718>. ¿Me he perdido algo?
RayLuo
Gracias por la respuesta . Al usar el código anterior, había transferido un archivo de imagen en bruto de 2,2 GB usando la solicitud PUT al servidor web.
Akshay Patil
4

Parece que las solicitudes de Python no manejan archivos de varias partes extremadamente grandes.

La documentación recomienda que lo investigue requests-toolbelt.

Aquí está la página pertinente de su documentación.

centeno
fuente
2

La biblioteca de carteles de Chris Atlee funciona muy bien para esto (particularmente la función de conveniencia poster.encode.multipart_encode()). Como beneficio adicional, admite la transmisión de archivos grandes sin cargar un archivo completo en la memoria. Ver también Python número 3244 .

gotgenes
fuente
2

Estoy tratando de probar la django rest api y está funcionando para mí:

def test_upload_file(self):
        filename = "/Users/Ranvijay/tests/test_price_matrix.csv"
        data = {'file': open(filename, 'rb')}
        client = APIClient()
        # client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
        response = client.post(reverse('price-matrix-csv'), data, format='multipart')

        print response
        self.assertEqual(response.status_code, status.HTTP_200_OK)
Ranvijay Sachan
fuente
1
Este código proporciona una pérdida de memoria: se olvidó de close()un archivo.
Chiefir
0

También es posible que desee echar un vistazo a httplib2 , con ejemplos . Creo que usar httplib2 es más conciso que usar los módulos HTTP integrados.

pdc
fuente
2
No hay ejemplos que muestren cómo lidiar con la carga de archivos.
Dland
El enlace está desactualizado + sin ejemplo en línea.
jlr
3
Desde entonces se ha movido a github.com/httplib2/httplib2 . Por otro lado, hoy en día probablemente recomendaría en su requestslugar.
pdc
0
def visit_v2(device_code, camera_code):
    image1 = MultipartParam.from_file("files", "/home/yuzx/1.txt")
    image2 = MultipartParam.from_file("files", "/home/yuzx/2.txt")
    datagen, headers = multipart_encode([('device_code', device_code), ('position', 3), ('person_data', person_data), image1, image2])
    print "".join(datagen)
    if server_port == 80:
        port_str = ""
    else:
        port_str = ":%s" % (server_port,)
    url_str = "http://" + server_ip + port_str + "/adopen/device/visit_v2"
    headers['nothing'] = 'nothing'
    request = urllib2.Request(url_str, datagen, headers)
    try:
        response = urllib2.urlopen(request)
        resp = response.read()
        print "http_status =", response.code
        result = json.loads(resp)
        print resp
        return result
    except urllib2.HTTPError, e:
        print "http_status =", e.code
        print e.read()
usuario6081103
fuente