Wyślij plik za pomocą POST ze skryptu Pythona

139

Czy istnieje sposób na wysłanie pliku przy użyciu POST ze skryptu w Pythonie?

Tylko czytać
źródło

Odpowiedzi:

214

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

Żądania bardzo ułatwiają przesyłanie plików zakodowanych w formacie Multipart:

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

Otóż ​​to. Nie żartuję - to jedna linia kodu. Plik został wysłany. Sprawdźmy:

>>> 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
źródło
2
Próbuję tego samego i działa dobrze, jeśli rozmiar pliku jest mniejszy niż ~ 1,5 MB. w przeciwnym razie generuje błąd ... proszę spojrzeć tutaj .
Niks Jain
1
próbuję zalogować się do jakiejś witryny za pomocą żądania, które wykonałem pomyślnie, ale teraz chcę przesłać wideo po zalogowaniu, a formularz ma inne pola do wypełnienia przed wysłaniem. Jak więc mam przekazać te wartości, takie jak opis filmu, tytuł filmu itp.
TaraGurung
15
Prawdopodobnie wolałbyś to zrobić with open('report.xls', 'rb') as f: r = requests.post('http://httpbin.org/post', files={'report.xls': f}), więc po otwarciu ponownie zamyka plik.
Hjulle
3
Co? Od kiedy wysyłanie wniosków jest takie proste?
palsch
1
Ta odpowiedź powinna zostać zaktualizowana, aby uwzględnić sugestię Hjulle'a dotyczącą użycia menedżera kontekstu, aby upewnić się, że plik jest zamknięty.
bmoran
28

Tak. Możesz użyć urllib2modułu i zakodować przy użyciu multipart/form-datatypu zawartości. Oto przykładowy kod na początek - to trochę więcej niż tylko przesyłanie plików, ale powinieneś być w stanie go przeczytać i zobaczyć, jak to działa:

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
źródło
1
W Pythonie 2.6.6 otrzymywałem błąd podczas wieloczęściowego analizowania granic podczas używania tego kodu w systemie Windows. Musiałem zmienić z string.letters na string.ascii_letters, jak omówiono na stackoverflow.com/questions/2823316/ ... aby to zadziałało. Wymóg dotyczący granicy omówiono tutaj: stackoverflow.com/questions/147451/ ...
amit
wywołanie run_upload ({'server': '', 'thread': ''}, path = ['/ path / to / file.txt']) powoduje błąd w tej linii: upload_file (path), ponieważ "upload file" wymaga 3 parametry, więc zastępuję je linią upload_file (path, 1, 1)
Radian
4

Jedyne co powstrzymuje Cię przed skorzystaniem z urlopen bezpośrednio na obiekcie pliku to fakt, że wbudowany obiekt plikowy nie ma definicji len . Prostym sposobem jest utworzenie podklasy, która zapewni urlopen z poprawnym plikiem. Zmodyfikowałem również nagłówek Content-Type w poniższym pliku.

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
źródło
@robert Testuję Twój kod w Pythonie 2.7, ale to nie działa. urlopen (Request (theUrl, theFile, ...)) po prostu koduje zawartość pliku jak zwykły post, ale nie może określić prawidłowego pola formularza. Próbuję nawet wariantu urlopen (theUrl, urlencode ({'serveride_field_name': EnhancedFile ('my_file.txt')})), ładuje plik ale (oczywiście!) Z niepoprawną treścią jako <otwórz plik 'my_file.txt', tryb 'r' pod adresem 0x00D6B718>. Przegapiłem coś?
RayLuo
Dziękuję za odpowiedź . Korzystając z powyższego kodu, przesłałem plik obrazu RAW o wielkości 2,2 GB za pomocą żądania PUT na serwer sieciowy.
Akshay Patil
4

Wygląda na to, że żądania Pythona nie obsługują bardzo dużych plików wieloczęściowych.

Dokumentacja zaleca zapoznanie się z requests-toolbelt.

Oto odpowiednia strona z ich dokumentacji.

żyto
źródło
2

Biblioteka plakatów Chrisa Atlee bardzo dobrze się do tego nadaje (szczególnie funkcja wygodna poster.encode.multipart_encode()). Dodatkowo obsługuje przesyłanie strumieniowe dużych plików bez ładowania całego pliku do pamięci. Zobacz także wydanie Pythona 3244 .

gotgenes
źródło
2

Próbuję przetestować api django rest i działa dla mnie:

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
źródło
1
ten kod powoduje wyciek pamięci - zapomniałeś close()o pliku.
Chiefir,
0

Możesz również rzucić okiem na httplib2 z przykładami . Uważam, że użycie httplib2 jest bardziej zwięzłe niż użycie wbudowanych modułów HTTP.

pdc
źródło
2
Nie ma przykładów pokazujących, jak radzić sobie z przesyłaniem plików.
dland
Link jest nieaktualny + brak wbudowanego przykładu.
jlr
3
Od tego czasu przeniósł się na github.com/httplib2/httplib2 . Z drugiej strony, obecnie prawdopodobnie poleciłbym requestszamiast tego.
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
źródło