Senden Sie eine Datei mit POST aus einem Python-Skript

139

Gibt es eine Möglichkeit, eine Datei mit POST aus einem Python-Skript zu senden?

Schreibgeschützt
quelle

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": ""
}
Piotr Dobrogost
quelle
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!')
John Millikin
quelle
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
Ilmarinen
quelle
@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.

Roggen
quelle
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 .

gotgenes
quelle
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)
Ranvijay Sachan
quelle
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.

pdc
quelle
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()
user6081103
quelle