Jak wysłać „multipart / form-data” z żądaniami w pythonie?

211

Jak wysłać multipart/form-datazapytanie w pythonie? Jak wysłać plik, rozumiem, ale jak wysłać dane formularza tą metodą, nie mogę zrozumieć.

agrynchuk
źródło
twoje pytanie nie jest do końca jasne. Co chcesz osiągnąć Czy chcesz wysłać „dane wieloczęściowe / formularz” bez przesyłania pliku w formularzu?
Hans Następnie
4
Fakt, że filesparametr jest używany do obu tych zadań, jest bardzo złym API. Podniosłem problem zatytułowany Wysyłanie danych wieloczęściowych - potrzebujemy lepszego interfejsu API, aby to naprawić. Jeśli zgadzasz się, że użycie filesparametru do wysyłania danych wielopartyjnych jest co najmniej mylące, poproś o zmianę interfejsu API w powyższym problemie.
Piotr Dobrogost
@PiotrDobrogost ten problem jest zamknięty. Nie zachęcaj ludzi do komentowania zamkniętych kwestii, istotnych lub innych.
Ian Stapleton Cordasco
1
Nieważne, właśnie zdałem sobie sprawę, że twój komentarz został opublikowany, zanim został zamknięty. Nienawidzę tego, że StackOverflow nie utrzymuje porządku w porządku chronologicznym.
Ian Stapleton Cordasco

Odpowiedzi:

166

Zasadniczo, jeśli podasz filesparametr (słownik), wówczas requestswyśle multipart/form-datatest POST zamiast application/x-www-form-urlencodedPOST. Nie jesteś ograniczony do używania rzeczywistych plików w tym słowniku, jednak:

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

i httpbin.org informuje, z jakimi nagłówkami napisałeś; w response.json()mamy:

>>> 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'}

Co więcej, możesz dalej kontrolować nazwę pliku, typ zawartości i dodatkowe nagłówki dla każdej części za pomocą krotki zamiast pojedynczego ciągu znaków lub obiektu bajtów. Krotka powinna zawierać od 2 do 4 elementów; nazwa pliku, treść, opcjonalnie typ zawartości i opcjonalny słownik dalszych nagłówków.

Użyłbym formularza krotkowego z Nonenazwą pliku, aby filename="..."parametr został usunięty z żądania dla tych części:

>>> 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 może być również listą krotek o dwóch wartościach, jeśli potrzebujesz zamówienia i / lub wielu pól o tej samej nazwie:

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

Jeśli podasz zarówno filesa data, to zależy od wartości zdata co zostanie użyte do utworzenia treści testu POST. Jeśli datajest łańcuchem, zostanie użyty tylko ten ciąg; w przeciwnym razie oba datai filessą używane, z elementami datawymienionymi na początku.

Istnieje również doskonały requests-toolbeltprojekt, który obejmuje zaawansowane wsparcie Multipart . Przyjmuje definicje pól w tym samym formacie co filesparametr, ale w przeciwieństwie dorequests do domyślnie nie ustawia parametru nazwy pliku. Ponadto może przesyłać strumieniowo żądanie z otwartych obiektów plikowych, gdzie requestsnajpierw skonstruuje treść żądania w pamięci:

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}
)

Pola są zgodne z tymi samymi konwencjami; użyj krotki zawierającej od 2 do 4 elementów, aby dodać nazwę pliku, część typu MIME lub dodatkowe nagłówki. W przeciwieństwie do filesparametru, nie filenamepodejmuje się próby znalezienia wartości domyślnej, jeśli nie zostanie użyta krotka.

Martijn Pieters
źródło
3
Jeśli używane są pliki = {}, nie można używać nagłówków = {'Content-Type': 'bla blah'}!
zaki
5
@zaki: w rzeczywistości, ponieważ multipart/form-dataContent-Type musi zawierać wartość graniczną używaną do deliniowania części w treści postu. Brak ustawienia Content-Typenagłówka gwarantuje, że requestsustawi on prawidłową wartość.
Martijn Pieters
Ważna uwaga: żądanie zostanie wysłane tylko tak, multipart/form-datajakby wartość files=true była prawdziwa, więc jeśli chcesz wysłać multipart/form-datażądanie, ale nie zawierasz żadnych plików, możesz ustawić prawdziwą, ale bez znaczenia wartość, taką jak {'':''}i ustawić data=w treści żądania. Jeśli to robisz, nie udostępniaj Content-Typenagłówka samodzielnie; requestsustawię to dla ciebie. Prawdę możesz sprawdzić tutaj: github.com/psf/requests/blob/…
Daniel Situnayake
@DanielSitunayake nie ma potrzeby takiego hackowania. Po prostu wstaw wszystkie pola do filesdykta, nie muszą to być pliki (po prostu użyj formularza krotki i ustaw nazwę pliku na None). Jeszcze lepiej, skorzystaj z requests_toolbeltprojektu.
Martijn Pieters
Dzięki @MartijnPieters, sztuczka z formularzem krotki jest świetna! Spróbuje.
Daniel Situnayake
107

Od czasu napisania poprzednich odpowiedzi żądania uległy zmianie. Zajrzyj do wątku błędów w Github, aby uzyskać więcej szczegółów i ten komentarz na przykład.

Krótko mówiąc, parametr files przyjmuje a dictz kluczem będącym nazwą pola formularza, a wartością jest albo ciąg znaków, albo krotka o długości 2, 3 lub 4, jak opisano w sekcji POST pliku wieloczęściowego w żądaniach szybki start:

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

W powyższym krotka składa się w następujący sposób:

(filename, data, content_type, headers)

Jeśli wartość jest tylko ciągiem, nazwa pliku będzie taka sama jak klucz, jak poniżej:

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

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

72c2b6f406cdabd578c5fd7598557c52

Jeśli wartością jest krotka, a pierwszym wpisem jest Nonenazwa pliku, właściwość nie zostanie uwzględniona:

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

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

72c2b6f406cdabd578c5fd7598557c52
runejuhl
źródło
2
Co zrobić, jeśli trzeba odróżnić namei filenameale także wiele pól o tej samej nazwie?
Michael
1
Mam podobny problem jak @Michael. Czy możesz spojrzeć na pytanie i zasugerować coś? [link] ( stackoverflow.com/questions/30683352/... )
Shaardool,
czy ktoś rozwiązał ten problem, mając wiele pól o tej samej nazwie?
user3131037,
1
Sztuką przekazać en pusty ciąg jako pierwsza wartość fileskrotki już nie działa: trzeba użyć requests.post dataparametru zamiast wysłać źródło dodatkowego zakaz plików multipart/form-dataparametrów
Lucas Kimona
1
Przekazywanie Nonezamiast pustego ciągu wydaje się działać
Alexandre Blin
73

Musisz użyć tego filesparametru, aby wysłać wieloczęściowe żądanie POST, nawet jeśli nie musisz przesyłać żadnych plików.

Z oryginalnego źródła żądań :

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.

Odpowiednia część to: file-tuple can be a2-tuple, .3-tupleor a4-tuple

Na podstawie powyższego najprostsze wieloczęściowe żądanie formularza, które zawiera zarówno pliki do przesłania, jak i pola formularza, będzie wyglądać następująco:

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)

Zauważ Nonejako pierwszy argument w krotce dla pól zwykłego tekstu - jest to symbol zastępczy pola nazwy pliku, który jest używany tylko do przesyłania plików, ale dla pól tekstowych przekazywanych, Noneponieważ pierwszy parametr jest wymagany do przesłania danych .

Wiele pól o tej samej nazwie

Jeśli musisz opublikować wiele pól o tej samej nazwie, zamiast słownika możesz zdefiniować ładunek jako listę (lub krotkę) krotek:

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

Interfejs API żądań przesyłania strumieniowego

Jeśli powyższy interfejs API nie jest dla Ciebie wystarczający w Pythonie, zastanów się nad użyciem żądania toolbelt ( pip install requests_toolbelt), który jest rozszerzeniem podstawowego modułu żądań , który zapewnia obsługę przesyłania strumieniowego plików, a także MultipartEncoder, którego można użyć zamiast niego filesi który pozwala definiujesz ładunek jako słownik, krotkę lub listę.

MultipartEncodermoże być używany zarówno do żądań wieloczęściowych z lub bez rzeczywistych pól przesyłania. Musi być przypisany do dataparametru.

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})

Jeśli musisz wysłać wiele pól o tej samej nazwie lub jeśli kolejność pól formularza jest ważna, zamiast słownika można użyć krotki lub listy:

multipart_data = MultipartEncoder(
    fields=(
            ('action', 'ingest'), 
            ('item', 'spam'),
            ('item', 'sausage'),
            ('item', 'eggs'),
           )
    )
ccpizza
źródło
Dziękuję Ci za to. Kolejność kluczy była dla mnie ważna, a to bardzo pomogło.
Splendor
Niesamowity. W niewytłumaczalny sposób interfejs API, z którym pracuję, wymaga 2 różnych wartości dla tego samego klucza. To jest niesamowite. Dziękuję Ci.
ajon
@ccpizza, co właściwie oznacza ta linia? > „(„ plik.py ”, otwórz („ plik.py ”,„ rb ”),„ tekst / zwykły ”)”. Dla mnie to nie działa :(
Denis Koreyba 31.01.17
@DenisKoreyba: jest to przykład pola do przesyłania pliku, które zakłada, że ​​nazwany plik file.pyznajduje się w tym samym folderze co skrypt.
ccpizza
1
Możesz użyć Nonezamiast pustego ciągu. Wówczas żądania w ogóle nie będą zawierać nazwy pliku. Więc zamiast Content-Disposition: form-data; name="action"; filename=""tego będzie Content-Disposition: form-data; name="action". Było to dla mnie krytyczne, aby serwer zaakceptował te pola jako pola formularza, a nie jako pliki.
Mitar,
8

Oto prosty fragment kodu umożliwiający przesłanie pojedynczego pliku z dodatkowymi parametrami za pomocą żądań:

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)

Pamiętaj, że nie musisz jawnie określać żadnego rodzaju treści.

UWAGA: Chciałem skomentować jedną z powyższych odpowiedzi, ale nie mógł z powodu niskiej reputacji, dlatego opracowałem tutaj nową odpowiedź.

Jainik
źródło
4

Musisz użyć nameatrybutu pliku do przesłania, który znajduje się w kodzie HTML witryny. Przykład:

autocomplete="off" name="image">

Widzisz name="image">? Możesz go znaleźć w kodzie HTML strony do przesłania pliku. Musisz go użyć do przesłania plikuMultipart/form-data

scenariusz:

import requests

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

W miejscu obrazu dodaj nazwę pliku do przesłania w formacie HTML

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

Jeśli przesyłanie wymaga kliknięcia przycisku przesyłania, możesz użyć w ten sposób:

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

Następnie uruchom żądanie

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

I gotowe, plik przesłany pomyślnie

Skiller Dz
źródło
3

Wyślij klucz i wartość danych wieloczęściowych / formularzy

polecenie zwijania:

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

zapytania python - bardziej skomplikowane żądania POST :

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

Wyślij plik danych wieloczęściowych / formularzy

polecenie zwijania:

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

zapytania w języku Python - POST pliku wieloczęściowego :

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

to wszystko.

Crifan
źródło
-1

Oto fragment kodu w pythonie, który musisz przesłać jeden duży pojedynczy plik jako dane wieloczęściowe. Z oprogramowaniem pośrednim NodeJs Multer działającym po stronie serwera.

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

Po stronie serwera sprawdź dokumentację multera pod adresem : https://github.com/expressjs/multer tutaj pojedynczy pole („fieldName”) służy do akceptacji jednego pliku, jak w:

var upload = multer().single('fieldName');
vinaymk
źródło