Envoyer un fichier en utilisant POST à ​​partir d'un script Python

139

Existe-t-il un moyen d'envoyer un fichier en utilisant POST à ​​partir d'un script Python?

Lecture seulement
la source

Réponses:

214

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

Les requêtes simplifient le téléchargement de fichiers codés en plusieurs parties:

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

C'est tout. Je ne plaisante pas - c'est une ligne de code. Le fichier a été envoyé. Allons vérifier:

>>> 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
la source
2
J'essaie la même chose et cela fonctionne bien si la taille du fichier est inférieure à ~ 1,5 Mo. sinon, il lance une erreur .. veuillez regarder ici .
Niks Jain
1
ce que j'essaie de faire est de me connecter à un site en utilisant une demande que j'ai faite avec succès mais maintenant je veux télécharger une vidéo après la connexion et le formulaire a des champs différents à remplir avant la soumission. Alors, comment dois-je transmettre ces valeurs telles que la description des vidéos, le titre des vidéos, etc.
TaraGurung
15
Vous voudrez probablement faire à la with open('report.xls', 'rb') as f: r = requests.post('http://httpbin.org/post', files={'report.xls': f})place, donc il ferme à nouveau le fichier après ouverture.
Hjulle
3
Hein? Depuis quand envoyer des demandes est-il si simple?
palsch le
1
Cette réponse doit être mise à jour pour inclure la suggestion de Hjulle d'utiliser le gestionnaire de contexte pour s'assurer que le fichier est fermé.
bmoran
28

Oui. Vous utiliseriez leurllib2 module et encoderiez en utilisant le multipart/form-datatype de contenu. Voici un exemple de code pour vous aider à démarrer - c'est un peu plus qu'un simple téléchargement de fichiers, mais vous devriez pouvoir le lire et voir comment cela fonctionne:

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
la source
1
Sur python 2.6.6, j'obtenais une erreur dans l'analyse des limites Multipart lors de l'utilisation de ce code sous Windows. J'ai dû passer de string.letters à string.ascii_letters comme indiqué sur stackoverflow.com/questions/2823316/… pour que cela fonctionne. L'exigence sur la limite est discutée ici: stackoverflow.com/questions/147451
...
appeler run_upload ({'serveur': '', 'thread': ''}, chemins = ['/ chemin / vers / fichier.txt']) provoque une erreur dans cette ligne: upload_file (chemin) car "upload file" nécessite 3 paramètres donc je le remplace par cette ligne upload_file (path, 1, 1)
Radian
4

La seule chose qui vous empêche d'utiliser urlopen directement sur un objet fichier est le fait que l'objet fichier intégré n'a pas de définition len . Un moyen simple consiste à créer une sous-classe, qui fournit à urlopen le fichier correct. J'ai également modifié l'en-tête Content-Type dans le fichier ci-dessous.

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
la source
@robert Je teste votre code en Python2.7 mais cela ne fonctionne pas. urlopen (Request (theUrl, theFile, ...)) encode simplement le contenu du fichier comme s'il s'agissait d'une publication normale mais ne peut pas spécifier le champ de formulaire correct. J'essaye même la variante urlopen (theUrl, urlencode ({'serverside_field_name': EnhancedFile ('my_file.txt')})), elle télécharge un fichier mais (bien sûr!) Avec un contenu incorrect comme <fichier ouvert 'my_file.txt', mode 'r' à 0x00D6B718>. Ai-je oublié quelque chose?
RayLuo
Merci d'avoir répondu . En utilisant le code ci-dessus, j'avais transféré un fichier d'image brute de 2,2 Go en utilisant la requête PUT dans le serveur Web.
Akshay Patil
4

Il semble que les requêtes python ne gèrent pas les fichiers en plusieurs parties extrêmement volumineux.

La documentation vous recommande d'examiner requests-toolbelt.

Voici la page pertinente de leur documentation.

seigle
la source
2

La bibliothèque d' affiches de Chris Atlee fonctionne très bien pour cela (en particulier la fonction de commodité poster.encode.multipart_encode()). En prime, il prend en charge la diffusion de gros fichiers sans charger un fichier entier en mémoire. Voir aussi le problème Python 3244 .

gotgenes
la source
2

J'essaye de tester django rest api et cela fonctionne pour moi:

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
la source
1
ce code fournit une fuite de mémoire - vous avez oublié close()un fichier.
Chiefir
0

Vous voudrez peut-être aussi jeter un œil à httplib2 , avec des exemples . Je trouve que l'utilisation de httplib2 est plus concise que l'utilisation des modules HTTP intégrés.

PDC
la source
2
Il n'y a aucun exemple qui montre comment gérer les téléchargements de fichiers.
dland
Le lien est obsolète + aucun exemple en ligne.
jlr
3
Il a depuis été déplacé vers github.com/httplib2/httplib2 . D'un autre côté, de nos jours, je recommanderais probablement requestsplutôt.
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()
user6081103
la source