Senden Sie eine Datei mit POST aus einem Python-Skript


Antworten:


213

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

Anfragen machen das Hochladen von mehrteilig codierten Dateien sehr einfach:

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

Das ist es. Ich scherze nicht - das ist eine Codezeile. Die Datei wurde gesendet. Lass uns das Prüfen:

>>> 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
Ich versuche das gleiche und es funktioniert gut, wenn die Dateigröße weniger als ~ 1,5 MB beträgt. sonst wirft es einen Fehler .. bitte schauen Sie hier .
Niks Jain

1
Ich versuche mich auf einer Website mit einer Anfrage anzumelden, die ich erfolgreich durchgeführt habe. Jetzt möchte ich nach dem Anmelden ein Video hochladen und das Formular enthält andere Felder, die vor dem Absenden ausgefüllt werden müssen. Also, wie soll ich diese Werte wie
Videobeschreibung, Videotitel

15
Sie möchten dies wahrscheinlich with open('report.xls', 'rb') as f: r = requests.post('http://httpbin.org/post', files={'report.xls': f})stattdessen tun , damit die Datei nach dem Öffnen wieder geschlossen wird.
Hjulle

3
Huh? Seit wann ist das Senden von Anfragen so einfach?
Palsch

1
Diese Antwort sollte aktualisiert werden, um den Vorschlag von Hjulle aufzunehmen, den Kontextmanager zu verwenden, um sicherzustellen, dass die Datei geschlossen ist.
Bmoran

28

Ja. Sie würden das urllib2Modul verwenden und mit dem multipart/form-dataInhaltstyp codieren . Hier ist ein Beispielcode für den Einstieg - es ist ein bisschen mehr als nur das Hochladen von Dateien, aber Sie sollten in der Lage sein, ihn durchzulesen und zu sehen, wie er funktioniert:

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
Unter Python 2.6.6 wurde beim Verwenden dieses Codes unter Windows ein Fehler beim Parsen von mehrteiligen Grenzen angezeigt. Ich musste von string.letters zu string.ascii_letters wechseln, wie unter stackoverflow.com/questions/2823316/… beschrieben, damit dies funktioniert. Die Anforderung an die Grenze wird hier diskutiert: stackoverflow.com/questions/147451/…
amit

Das Aufrufen von run_upload ({'server': '', 'thread': ''}, path = ['/ path / to / file.txt']) verursacht einen Fehler in dieser Zeile: upload_file (path), da "upload upload" erforderlich ist 3 Parameter, also ersetze ich es durch diese Zeile upload_file (Pfad, 1, 1)
Radian

4

Das einzige, was Sie davon abhält, urlopen direkt für ein Dateiobjekt zu verwenden, ist die Tatsache, dass dem integrierten Dateiobjekt eine len- Definition fehlt . Eine einfache Möglichkeit besteht darin, eine Unterklasse zu erstellen, die urlopen mit der richtigen Datei versorgt. Ich habe auch den Content-Type-Header in der folgenden Datei geändert.

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 Ich teste deinen Code in Python2.7, aber es funktioniert nicht. urlopen (Request (theUrl, theFile, ...)) codiert lediglich den Inhalt der Datei wie einen normalen Beitrag, kann jedoch nicht das richtige Formularfeld angeben. Ich versuche sogar die Variante urlopen (theUrl, urlencode ({'serverside_field_name': EnhancedFile ('my_file.txt')})), es lädt eine Datei hoch, aber (natürlich!) Mit falschem Inhalt als <open file 'my_file.txt', Modus 'r' bei 0x00D6B718>. Habe ich etwas verpasst?
RayLuo

Danke für die Antwort . Mit dem obigen Code hatte ich eine 2,2-GB-Rohbilddatei mithilfe einer PUT-Anforderung auf den Webserver übertragen.
Akshay Patil

4

Es sieht so aus, als würden Python-Anfragen keine extrem großen mehrteiligen Dateien verarbeiten.

In der Dokumentation wird empfohlen, einen Blick darauf zu werfen requests-toolbelt.

Hier ist die relevante Seite aus ihrer Dokumentation.


2

Die Poster- Bibliothek von Chris Atlee eignet sich sehr gut dafür (insbesondere für die Convenience-Funktion poster.encode.multipart_encode()). Als Bonus unterstützt es das Streaming großer Dateien, ohne eine ganze Datei in den Speicher zu laden. Siehe auch Python-Ausgabe 3244 .


2

Ich versuche, Django Rest API zu testen und es funktioniert für mich:

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
Dieser Code führt zu einem Speicherverlust - Sie haben close()eine Datei vergessen .
Chiefir

0

Vielleicht möchten Sie auch einen Blick auf httplib2 mit Beispielen werfen . Ich finde, dass die Verwendung von httplib2 prägnanter ist als die Verwendung der integrierten HTTP-Module.


2
Es gibt keine Beispiele, die zeigen, wie mit Datei-Uploads umgegangen wird.
Dland

Link ist veraltet + kein Inline-Beispiel.
jlr

3
Es wurde inzwischen auf github.com/httplib2/httplib2 verschoben . Andererseits würde ich heutzutage wahrscheinlich requestsstattdessen empfehlen .
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()
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.