Invia file usando POST da uno script Python


Risposte:


214

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

Le richieste rendono molto semplice il caricamento di file con codifica multipart:

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

Questo è tutto. Non sto scherzando: questa è una riga di codice. Il file è stato inviato. Controlliamo:

>>> 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": ""
}

2
Sto provando la stessa cosa e funziona bene se la dimensione del file è inferiore a ~ 1,5 MB. altrimenti sta generando un errore .. per favore guarda qui .
Niks Jain,

1
quello che sto cercando di fare è accedere ad alcuni siti usando la richiesta che ho fatto con successo ma ora voglio caricare un video dopo aver effettuato l'accesso e il modulo ha campi diversi da compilare prima dell'invio. Quindi, come dovrei passare quei valori come la descrizione dei video, il titolo dei video, ecc.
TaraGurung,

15
Probabilmente vorrai fare with open('report.xls', 'rb') as f: r = requests.post('http://httpbin.org/post', files={'report.xls': f})invece, quindi chiude nuovamente il file dopo l'apertura.
Hjulle,

3
Eh? Da quando inviare richieste è così semplice?
Palsch,

1
Questa risposta dovrebbe essere aggiornata per includere il suggerimento di Hjulle di usare il gestore del contesto per assicurarsi che il file sia chiuso.
bmoran,

28

Sì. Utilizzeresti il urllib2modulo e codificheresti utilizzando il multipart/form-datatipo di contenuto. Ecco un codice di esempio per iniziare: è un po 'più di un semplice caricamento di file, ma dovresti essere in grado di leggerlo e vedere come funziona:

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!')

1
Su python 2.6.6 ho riscontrato un errore nell'analisi dei limiti Multipart durante l'utilizzo di questo codice su Windows. Ho dovuto cambiare da string.letters a string.ascii_letters come discusso in stackoverflow.com/questions/2823316/... per questo al lavoro. Il requisito di confine è discusso qui: stackoverflow.com/questions/147451/...
Amit

chiamando run_upload ({'server': '', 'thread': ''}, percorsi = ['/ percorso / a / file.txt']) causa errore in questa riga: upload_file (percorso) perché "upload file" richiede 3 parametri quindi lo sostituisco con questa riga upload_file (percorso, 1, 1)
Radian

4

L'unica cosa che ti impedisce di usare urlopen direttamente su un oggetto file è il fatto che l'oggetto file incorporato non ha una definizione len . Un modo semplice è creare una sottoclasse, che fornisce a urlopen il file corretto. Ho anche modificato l'intestazione Content-Type nel file seguente.

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

@robert Ho testato il tuo codice in Python2.7 ma non funziona. urlopen (Request (theUrl, theFile, ...)) codifica semplicemente il contenuto del file come se fosse un post normale ma non può specificare il campo modulo corretto. Provo anche la variante urlopen (theUrl, urlencode ({'serveride_field_name': EnhancedFile ('my_file.txt')})), carica un file ma (ovviamente!) Con contenuto errato come <apri file 'my_file.txt', modalità 'r' a 0x00D6B718>. Ho dimenticato qualcosa?
RayLuo

Grazie per la risposta . Usando il codice sopra ho trasferito il file di immagine non elaborata da 2,2 GB utilizzando la richiesta PUT nel server web.
Akshay Patil,

4

Sembra che le richieste di Python non gestiscano file multi-part estremamente grandi.

La documentazione consiglia di esaminare requests-toolbelt.

Ecco la pagina pertinente dalla loro documentazione.


2

La libreria di poster di Chris Atlee funziona davvero bene per questo (in particolare la funzione convenienza poster.encode.multipart_encode()). Come bonus, supporta lo streaming di file di grandi dimensioni senza caricare un intero file in memoria. Vedi anche il numero 3244 di Python .


2

Sto cercando di testare django rest api e il suo funzionamento per me:

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)

1
questo codice fornisce una perdita di memoria - hai dimenticato close()un file.
Chiefir

0

Potresti anche dare un'occhiata a httplib2 , con esempi . Trovo che l'utilizzo di httplib2 sia più conciso rispetto all'utilizzo dei moduli HTTP integrati.


2
Non ci sono esempi che mostrano come gestire i caricamenti di file.
dland,

Il link è obsoleto + nessun esempio incorporato.
jlr,

3
Da allora è passato a github.com/httplib2/httplib2 . D'altra parte, al giorno d'oggi probabilmente consiglierei requestsinvece.
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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.