Отправить файл с помощью POST из скрипта Python

Ответы:

219

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

Запросы упрощают загрузку файлов с кодировкой Multipart:

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

Вот и все. Я не шучу - это одна строчка кода. Файл отправлен. Давай проверим:

>>> 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
Я пытаюсь сделать то же самое, и он работает нормально, если размер файла меньше ~ 1,5 МБ. иначе выдает ошибку .. пожалуйста, посмотрите здесь .
Niks Jain
1
то, что я пытаюсь сделать, - это войти на какой-то сайт, используя запрос, который я успешно выполнил, но теперь я хочу загрузить видео после входа в систему, и в форме есть другие поля, которые необходимо заполнить перед отправкой. Итак, как мне передать такие значения, как описание видео, название видео и т. Д.
TaraGurung
15
Вы, вероятно, захотите сделать это with open('report.xls', 'rb') as f: r = requests.post('http://httpbin.org/post', files={'report.xls': f})вместо этого, чтобы он снова закрыл файл после открытия.
Hjulle
3
А? С каких это пор при отправке запросов все так просто?
palsch
1
Этот ответ должен быть обновлен, чтобы включить предложение Hjulle об использовании диспетчера контекста для обеспечения закрытия файла.
bmoran
28

Да. Вы должны использовать urllib2модуль и кодировать с использованием multipart/form-dataтипа контента. Вот пример кода для начала - это немного больше, чем просто загрузка файлов, но вы должны иметь возможность прочитать его и посмотреть, как это работает:

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
На python 2.6.6 я получал ошибку при синтаксическом разборе границ Multipart при использовании этого кода в Windows. Мне пришлось перейти с string.letters на string.ascii_letters, как обсуждалось на stackoverflow.com/questions/2823316/…, чтобы это сработало. Требование к границе обсуждается здесь: stackoverflow.com/questions/147451/…
amit
вызов run_upload ({'server': '', 'thread': ''}, paths = ['/ path / to / file.txt']) вызывает ошибку в этой строке: upload_file (путь), поскольку для "файла загрузки" требуется 3 параметра, поэтому я заменяю его этой строкой upload_file (path, 1, 1)
tabdulradi
4

Единственное, что мешает вам использовать urlopen непосредственно для файлового объекта, это то, что у встроенного файлового объекта отсутствует определение len . Простой способ - создать подкласс, который предоставляет urlopen с правильным файлом. Я также изменил заголовок Content-Type в файле ниже.

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 Я тестирую ваш код на Python2.7, но он не работает. urlopen (Request (theUrl, theFile, ...)) просто кодирует содержимое файла, как если бы это была обычная запись, но не может указать правильное поле формы. Я даже пробую вариант urlopen (theUrl, urlencode ({'serveride_field_name': EnhancedFile ('my_file.txt')})), он загружает файл, но (конечно!) С некорректным содержимым как <open file 'my_file.txt', режим 'r' в 0x00D6B718>. Я что-то пропустил?
RayLuo 04
Спасибо за ответ . Используя приведенный выше код, я перенес 2,2 ГБ необработанного файла изображения с помощью запроса PUT на веб-сервер.
Акшай Патил
4

Похоже, что запросы python не обрабатывают очень большие файлы, состоящие из нескольких частей.

В документации рекомендуется заглянуть в requests-toolbelt.

Вот соответствующая страница из их документации.

рожь
источник
2

Библиотека плакатов Криса Атли отлично подходит для этого (особенно удобная функция poster.encode.multipart_encode()). В качестве бонуса он поддерживает потоковую передачу больших файлов без загрузки всего файла в память. См. Также выпуск Python 3244 .

gotgenes
источник
2

Я пытаюсь протестировать django rest api и его работу для меня:

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
этот код обеспечивает утечку памяти - вы забыли close()файл.
Chiefir 03
0

Вы также можете посмотреть httplib2 с примерами . Я считаю, что использование httplib2 более лаконично, чем использование встроенных модулей HTTP.

pdc
источник
2
Нет примеров, показывающих, как поступать с загрузкой файлов.
dland
Ссылка устарела + нет встроенного примера.
jlr
3
С тех пор он переехал на github.com/httplib2/httplib2 . С другой стороны, в настоящее время я бы, вероятно, рекомендовал 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()
user6081103
источник