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ć.


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.


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

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'),
           )
    )

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ź.


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


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.


-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');
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.