Kirim file menggunakan POST dari skrip Python

139

Apakah ada cara untuk mengirim file menggunakan POST dari skrip Python?

Hanya baca
sumber

Jawaban:

214

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

Permintaan membuatnya sangat mudah untuk mengunggah file yang dikodekan multi-bagian:

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

Itu dia. Saya tidak bercanda - ini adalah satu baris kode. File telah dikirim. Mari kita periksa:

>>> 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
sumber
2
Saya mencoba hal yang sama & berfungsi baik jika ukuran file kurang dari ~ 1,5 MB. selain itu kesalahan yang terjadi .. silakan lihat di sini .
Niks Jain
1
apa yang saya coba lakukan adalah masuk ke beberapa situs menggunakan permintaan yang telah saya lakukan dengan sukses tetapi sekarang saya ingin mengunggah video setelah masuk dan formulir memiliki bidang yang berbeda untuk diisi sebelum pengiriman. Jadi bagaimana saya harus memberikan nilai-nilai seperti deskripsi video, judul video dll
TaraGurung
15
Anda mungkin ingin melakukannya with open('report.xls', 'rb') as f: r = requests.post('http://httpbin.org/post', files={'report.xls': f}), jadi itu akan menutup file lagi setelah dibuka.
Hjulle
3
Hah? Sejak kapan mengirim permintaan sangat sederhana?
palsch
1
Jawaban ini harus diperbarui untuk memasukkan saran Hjulle untuk menggunakan manajer konteks untuk memastikan file ditutup.
bmoran
28

Iya. Anda akan menggunakan urllib2modul, dan menyandikan menggunakan multipart/form-datatipe konten. Berikut ini beberapa contoh kode untuk Anda mulai - ini sedikit lebih dari sekadar mengunggah file, tetapi Anda harus dapat membacanya dan melihat cara kerjanya:

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
sumber
1
Pada python 2.6.6 saya mendapatkan kesalahan dalam parsing batas Multipart saat menggunakan kode ini pada Windows. Saya harus mengubah dari string.letters ke string.ascii_letters seperti yang dibahas di stackoverflow.com/questions/2823316/… agar ini berfungsi. Persyaratan tentang batas dibahas di sini: stackoverflow.com/questions/147451/…
amit
memanggil run_upload ({'server': '', 'thread': ''}, paths = ['/ path / ke / file.txt']) menyebabkan kesalahan pada baris ini: upload_file (path) karena "unggah file" memerlukan 3 parameter jadi saya menggantinya dengan baris ini upload_file (path, 1, 1)
Radian
4

Satu-satunya hal yang menghentikan Anda menggunakan urlopen secara langsung pada objek file adalah kenyataan bahwa objek file builtin tidak memiliki definisi len . Cara sederhana adalah membuat subclass, yang menyediakan urlopen dengan file yang benar. Saya juga telah memodifikasi header Tipe-Konten dalam file di bawah ini.

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
sumber
@robert Saya menguji kode Anda dalam Python2.7 tetapi tidak berhasil. urlopen (Request (theUrl, theFile, ...)) hanya menyandikan konten file seolah-olah posting normal tetapi tidak dapat menentukan bidang formulir yang benar. Saya bahkan mencoba varian urlopen (theUrl, urlencode ({'serverside_field_name': EnhancedFile ('my_file.txt')})), ia mengunggah file tetapi (tentu saja!) Dengan konten yang salah seperti <file terbuka 'my_file.txt', mode 'r' pada 0x00D6B718>. Apakah saya melewatkan sesuatu?
RayLuo
Terima kasih atas jawabannya . Dengan menggunakan kode di atas saya telah mentransfer file gambar mentah 2,2 GB dengan menggunakan permintaan PUT ke server web.
Akshay Patil
4

Sepertinya permintaan python tidak menangani file multi-bagian yang sangat besar.

Dokumentasi menyarankan Anda melihat requests-toolbelt.

Inilah halaman terkait dari dokumentasi mereka.

gandum hitam
sumber
2

Perpustakaan poster Chris Atlee bekerja sangat baik untuk ini (terutama fungsi kenyamanan poster.encode.multipart_encode()). Sebagai bonus, ini mendukung streaming file besar tanpa memuat seluruh file ke dalam memori. Lihat juga masalah Python 3244 .

Gotgenes
sumber
2

Saya mencoba untuk menguji api istirahat Django dan ini bekerja untuk saya:

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
sumber
1
kode ini memberikan kebocoran memori - Anda lupa close()file.
Chiefir
0

Anda juga mungkin ingin melihat httplib2 , dengan contoh - contoh . Saya menemukan menggunakan httplib2 lebih ringkas daripada menggunakan modul HTTP bawaan.

pdc
sumber
2
Tidak ada contoh yang menunjukkan cara menangani unggahan file.
dland
Tautan kedaluwarsa + tidak ada contoh inline
jlr
3
Sejak itu telah pindah ke github.com/httplib2/httplib2 . Di sisi lain, saat ini saya mungkin akan merekomendasikan requests.
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()
pengguna6081103
sumber