Python'daki isteklerle “çok parçalı / form verisi” nasıl gönderilir?


211

multipart/form-dataPython'da istekleri olan bir nasıl gönderilir ? Nasıl dosya gönderilir, anlıyorum, ancak form verilerinin bu yöntemle nasıl gönderileceğini anlayamıyorum.


sorunuz gerçekten açık değil. Ne elde etmek istiyorsun? Forma dosya yüklemeden "çok parçalı / form verisi" göndermek ister misiniz?
Hans Then

4
Aslında filesparametresi hem yapmak için kullanılan çok kötü bir API olduğunu. Çok parçalı veri gönderme başlıklı sorunu gündeme getirdim - bunu düzeltmek için daha iyi API'ye ihtiyacımız var . Çok filesparçalı veri göndermek için parametre kullanmanın en iyi şekilde yanıltıcı olduğunu kabul ediyorsanız , lütfen yukarıdaki sayıdaki API'yı değiştirmeyi isteyin.
Piotr Dobrogost

@PiotrDobrogost bu sorun kapatıldı. İnsanları ilgili veya başka şekilde kapalı konular hakkında yorum yapmaya teşvik etmeyin.
Ian Stapleton Cordasco

1
Boşver, yorumunun kapanmadan önce yayınlandığını fark ettim. StackOverflow'un işleri kronolojik sırada tutmamasından nefret ediyorum.
Ian Stapleton Cordasco

Yanıtlar:


167

Temel olarak, bir filesparametre (sözlük) belirtirseniz , POST yerine POST requestsgönderir . Ancak sözlüğdeki gerçek dosyaları kullanmakla sınırlı değilsiniz:multipart/form-dataapplication/x-www-form-urlencoded

>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200

ve httpbin.org hangi başlıklarla paylaşımda bulunduğunuzu bilmenizi sağlar; içinde response.json():

>>> from pprint import pprint
>>> pprint(response.json()['headers'])
{'Accept': '*/*',
 'Accept-Encoding': 'gzip, deflate',
 'Connection': 'close',
 'Content-Length': '141',
 'Content-Type': 'multipart/form-data; '
                 'boundary=c7cbfdd911b4e720f1dd8f479c50bc7f',
 'Host': 'httpbin.org',
 'User-Agent': 'python-requests/2.21.0'}

Daha da iyisi, tek bir dize veya bayt nesnesi yerine bir demet kullanarak her parça için dosya adını, içerik türünü ve ek başlıkları kontrol edebilirsiniz. Demetin 2 ila 4 element içermesi beklenir; dosya adı, içerik, isteğe bağlı olarak bir içerik türü ve isteğe bağlı olarak başka başlıkların sözlüğü.

Ben parametre bu parçalar için istek bırakılır Noneböylece dosya adı ile tuple formu kullanırdım filename="...":

>>> files = {'foo': 'bar'}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--bb3f05a247b43eede27a124ef8b968c5
Content-Disposition: form-data; name="foo"; filename="foo"

bar
--bb3f05a247b43eede27a124ef8b968c5--
>>> files = {'foo': (None, 'bar')}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--d5ca8c90a869c5ae31f70fa3ddb23c76
Content-Disposition: form-data; name="foo"

bar
--d5ca8c90a869c5ae31f70fa3ddb23c76--

files aynı ada sahip sipariş ve / veya birden fazla alana ihtiyacınız varsa, iki değerli grupların bir listesi olabilir:

requests.post(
    'http://requestb.in/xucj9exu',
    files=(
        ('foo', (None, 'bar')),
        ('foo', (None, 'baz')),
        ('spam', (None, 'eggs')),
    )
)

Her ikisini de belirtirseniz filesve datao zaman bağlıdır değerin bir dataPOST gövdesini oluşturmak için kullanılan ne olacağını. Eğer databir dizidir sadece kullanılan bir alanda yer alır; aksi takdirde her iki datave fileselemanlarla, kullanılan datailk listelenen.

Ayrıca gelişmiş Çok Parça Desteğirequests-toolbelt içeren mükemmel bir proje var . Alan tanımlarını parametreyle aynı biçimde alır , ancak aksine bir dosya adı parametresi ayarlamamıştır. Buna ek olarak, isteği ilk dosyada bellekte oluşturacak olan açık dosya nesnelerinden de akışı gerçekleştirebilir :filesrequestsrequests

from requests_toolbelt.multipart.encoder import MultipartEncoder

mp_encoder = MultipartEncoder(
    fields={
        'foo': 'bar',
        # plain file object, no filename or mime type produces a
        # Content-Disposition header with just the part name
        'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
    }
)
r = requests.post(
    'http://httpbin.org/post',
    data=mp_encoder,  # The MultipartEncoder is posted as data, don't use files=...!
    # The MultipartEncoder provides the content-type header with the boundary:
    headers={'Content-Type': mp_encoder.content_type}
)

Alanlar aynı kuralları takip eder; dosya adı, parça mime tipi veya ekstra üstbilgi eklemek için 2 ila 4 öğeden oluşan bir demet kullanın. filesParametrenin aksine, filenamebir demet kullanmazsanız varsayılan bir değer bulmaya çalışmaz.


3
Dosyalar = {} kullanılıyorsa, başlıklar = {'Content-Type': 'blah blah'} kullanılmamalıdır!
zaki

5
@zaki: gerçekten de, bacause multipart/form-dataContent-Type yazı gövdesindeki parçaları tanımlamak için kullanılan sınır değerini içermelidir . Content-TypeBaşlığın ayarlanmaması, requestsdoğru değere ayarlanmasını sağlar .
Martijn Pieters

Önemli not: istek yalnızca multipart/form-datadeğeri files=doğruymuş gibi gönderilecektir , bu nedenle bir multipart/form-dataistek göndermeniz gerekiyorsa, ancak herhangi bir dosya içermiyorsanız, gerçek ama anlamlı olmayan bir değer {'':''}ayarlayabilir data=ve istek gövdenizle ayarlayabilirsiniz . Bunu yapıyorsanız, Content-Typebaşlığı kendiniz vermeyin ; requestssizin için ayarlayacaktır. Doğruluk kontrolünü burada görebilirsiniz: github.com/psf/requests/blob/…
Daniel Situnayake

@DanielSitunayake böyle bir saldırıya gerek yok. Tüm alanları filesdikte edin, dosya olması gerekmez (sadece tuple formunu kullandığınızdan ve dosya adını ayarladığınızdan emin olun None). Daha da iyisi, requests_toolbeltprojeyi kullanın .
Martijn Pieters

Teşekkürler @MartijnPieters, demet formu ile hile harika! Bunu deneyeceğim.
Daniel Situnayake

107

Önceki cevaplar yazıldığından beri istekler değişti. Daha fazla ayrıntı için Github'daki hata dizisine ve bir örnek için bu yoruma bir göz atın .

Kısacası, files parametresi dict, anahtar, form alanının adı ve değer , isteklerde Çok Parçalı Kodlamalı Dosyayı POST bölümünde açıklandığı gibi, bir dize veya 2, 3 veya 4 uzunluklu bir demet olmak üzere a alır. hızlı başlangıç:

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

Yukarıda, demet aşağıdaki gibi oluşur:

(filename, data, content_type, headers)

Değer yalnızca bir dize ise, dosya adı aşağıdaki ile aynı olur:

>>> files = {'obvius_session_id': '72c2b6f406cdabd578c5fd7598557c52'}

Content-Disposition: form-data; name="obvius_session_id"; filename="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52

Değer bir demetse ve ilk girdi Nonedosyaadı özelliği dahil edilmezse :

>>> files = {'obvius_session_id': (None, '72c2b6f406cdabd578c5fd7598557c52')}

Content-Disposition: form-data; name="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52

2
Ne ayırt etmek gerekirse nameve filenameaynı zamanda aynı isimde birden çok alan var mı?
Michael

1
@Michael olarak benzer bir sorunum var. Soruyu inceleyip bir şeyler önerebilir misiniz? [bağlantı] ( stackoverflow.com/questions/30683352/… )
Shaardool

Birisi aynı adı taşıyan birden çok alana sahip olarak bu sorunu çözdü mü?
user3131037

1
Bir dizinin ilk değeri olarak en boş dizeyi geçme hilesi filesartık işe yaramıyor: requests.post dataek dosya dışı multipart/form-dataparametreler göndermek için parametre kullanmanız gerekiyor
Lucas Cimon

1
NoneBoş bir ip yerine geçmek işe yarıyor gibi görünüyor
Alexandre Blin

73

Herhangi bir dosya yüklemeniz gerekmediğinde bilefiles çok bölümlü bir POST isteği göndermek için parametreyi kullanmanız gerekir.

Orijinal istekler kaynağından:

def request(method, url, **kwargs):
    """Constructs and sends a :class:`Request <Request>`.

    ...
    :param files: (optional) Dictionary of ``'name': file-like-objects``
        (or ``{'name': file-tuple}``) for multipart encoding upload.
        ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``,
        3-tuple ``('filename', fileobj, 'content_type')``
        or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``,
        where ``'content-type'`` is a string
        defining the content type of the given file
        and ``custom_headers`` a dict-like object 
        containing additional headers to add for the file.

İlgili bölüm: file-tuple can be a2-tuple, .3-tupleor a4-tuple

Yukarıdakilere dayanarak, hem yüklenecek dosyaları hem de form alanlarını içeren en basit çok parçalı form isteği aşağıdaki gibi görünecektir:

multipart_form_data = {
    'file2': ('custom_file_name.zip', open('myfile.zip', 'rb')),
    'action': (None, 'store'),
    'path': (None, '/path1')
}

response = requests.post('https://httpbin.org/post', files=multipart_form_data)

print(response.content)

Not Nonedüz metin alanları için başlığın ilk argüman olarak - bu sadece dosya yüklemeleri için kullanılan dosya adı alanı için bir yer tutucudur ama geçen metin alanları için Noneveri sunulacak için ilk parametre olarak gereklidir .

Aynı ada sahip birden çok alan

Aynı ada sahip birden fazla alanı yayınlamanız gerekiyorsa, sözlük yerine yükünüzü bir grup listesi (veya bir demet) olarak tanımlayabilirsiniz:

multipart_form_data = (
    ('file2', ('custom_file_name.zip', open('myfile.zip', 'rb'))),
    ('action', (None, 'store')),
    ('path', (None, '/path1')),
    ('path', (None, '/path2')),
    ('path', (None, '/path3')),
)

Akış istekleri API'sı

Yukarıdaki API sizin için pythonic yeterli değilse, o zaman kullanmayı düşünün istekleri toolbelt ( pip install requests_toolbeltbir uzantısı olan) çekirdek istekleri bu dosya, yükleme yanı sıra Akış için destek sağlar modülü MultipartEncoder yerine kullanılabilir filesve hangi zamanda sağlar yükü sözlük, grup veya liste olarak tanımlarsınız.

MultipartEncoderhem gerçek yükleme alanları olan veya olmayan çok parçalı istekler için kullanılabilir. dataParametreye atanmalıdır .

import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder

multipart_data = MultipartEncoder(
    fields={
            # a file upload field
            'file': ('file.zip', open('file.zip', 'rb'), 'text/plain')
            # plain text fields
            'field0': 'value0', 
            'field1': 'value1',
           }
    )

response = requests.post('http://httpbin.org/post', data=multipart_data,
                  headers={'Content-Type': multipart_data.content_type})

Aynı ada sahip birden çok alan göndermeniz gerekiyorsa veya form alanlarının sırası önemliyse, sözlük yerine bir demet veya liste kullanılabilir:

multipart_data = MultipartEncoder(
    fields=(
            ('action', 'ingest'), 
            ('item', 'spam'),
            ('item', 'sausage'),
            ('item', 'eggs'),
           )
    )

Bunun için teşekkür ederim. Anahtarların sırası benim için önemliydi ve bu çok yardımcı oldu.
İhtişam

İnanılmaz. Açıkça, birlikte çalıştığım bir api aynı anahtar için 2 farklı değer gerektirir. Bu harika. Teşekkür ederim.
ajon

@ccpizza, bu çizgi aslında ne anlama geliyor? > "('file.py', açık ('file.py', 'rb'), 'metin / düz')". Benim için çalışmıyor :(
Denis Koreyba

@DenisKoreyba: Bu, adlandırılmış bir dosyanın file.pykomut dosyanızla aynı klasörde olduğunu varsayan bir dosya yükleme alanına örnektir .
17'de ccpizza

1
NoneBoş dize yerine kullanabilirsiniz . O zaman istekler hiçbir şekilde bir dosya adı içermez. Yani bunun yerine Content-Disposition: form-data; name="action"; filename=""olacak Content-Disposition: form-data; name="action". Sunucunun bu alanları dosya olarak değil form alanı olarak kabul etmesi benim için kritikti.
Mitar

9

İstekleri kullanarak ek parametrelerle tek bir dosya yüklemek için basit kod snippet'i şöyledir:

url = 'https://<file_upload_url>'
fp = '/Users/jainik/Desktop/data.csv'

files = {'file': open(fp, 'rb')}
payload = {'file_id': '1234'}

response = requests.put(url, files=files, data=payload, verify=False)

Lütfen herhangi bir içerik türü belirtmeniz gerekmediğini unutmayın.

NOT: Yukarıdaki cevaplardan biri hakkında yorum yapmak istedim ama düşük itibar nedeniyle bu yüzden burada yeni bir yanıt hazırladı olamazdı.


4

nameSitenin HTML'sindeki yükleme dosyasının niteliğini kullanmanız gerekir . Misal:

autocomplete="off" name="image">

Gördün mü name="image">? Dosyayı yüklemek için sitenin HTML'sinde bulabilirsiniz. Dosyayı ile yüklemek için kullanmanız gerekir.Multipart/form-data

senaryo:

import requests

site = 'https://prnt.sc/upload.php' # the site where you upload the file
filename = 'image.jpg'  # name example

Burada, resmin yerine HTML'deki yükleme dosyasının adını ekleyin

up = {'image':(filename, open(filename, 'rb'), "multipart/form-data")}

Yükleme için yükleme düğmesini tıklamanız gerekiyorsa, aşağıdakileri kullanabilirsiniz:

data = {
     "Button" : "Submit",
}

Sonra isteği başlat

request = requests.post(site, files=up, data=data)

Ve bitti, dosya başarıyla yüklendi


3

Çok parçalı / form verisi anahtarı ve değeri gönderme

curl komutu:

curl -X PUT http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' \
-F taskStatus=1

python istekleri - Daha karmaşık POST istekleri :

    updateTaskUrl = "http://127.0.0.1:8080/api/xxx"
    updateInfoDict = {
        "taskStatus": 1,
    }
    resp = requests.put(updateTaskUrl, data=updateInfoDict)

Çok parçalı / form-veri dosyası gönderme

curl komutu:

curl -X POST http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' \
-F file=@/Users/xxx.txt

python istekleri - Çok Parçalı Kodlamalı Dosya POST :

    filePath = "/Users/xxx.txt"
    fileFp = open(filePath, 'rb')
    fileInfoDict = {
        "file": fileFp,
    }
    resp = requests.post(uploadResultUrl, files=fileInfoDict)

bu kadar.


-1

Büyük tek bir dosyayı çok parçalı form verileri olarak yüklemeniz gereken python snippet'i. Sunucu tarafında çalışan NodeJs Multer ara katman yazılımı ile.

import requests
latest_file = 'path/to/file'
url = "http://httpbin.org/apiToUpload"
files = {'fieldName': open(latest_file, 'rb')}
r = requests.put(url, files=files)

Sunucu tarafı için lütfen multer belgelerine bakın: https://github.com/expressjs/multer burada tek alan ('fieldName') aşağıdaki gibi tek bir dosyayı kabul etmek için kullanılır:

var upload = multer().single('fieldName');
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.