379 Stimmen

Wie man ein "multipart/form-data" mit Anfragen in Python senden?

Wie versendet man eine multipart/form-data avec requests in Python? Wie man eine Datei sendet, verstehe ich, aber wie man die Formulardaten durch diese Methode senden kann nicht verstehen.

359voto

Martijn Pieters Punkte 953257

Grundsätzlich gilt: Wenn Sie eine files Parameter (ein Wörterbuch), dann requests sendet eine multipart/form-data POST anstelle einer application/x-www-form-urlencoded POST. Sie sind jedoch nicht darauf beschränkt, tatsächliche Dateien in diesem Wörterbuch zu verwenden:

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

und httpbin.org lässt Sie wissen, mit welchen Headern Sie gepostet haben; in response.json() haben wir:

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

Noch besser ist, dass Sie den Dateinamen, den Inhaltstyp und die zusätzlichen Kopfzeilen für jeden Teil weiter steuern können, indem Sie ein Tupel anstelle einer einzelnen Zeichenkette oder eines Byte-Objekts verwenden. Das Tupel sollte zwischen 2 und 4 Elemente enthalten: den Dateinamen, den Inhalt, optional einen Inhaltstyp und ein optionales Wörterbuch mit weiteren Kopfzeilen.

Ich würde die Tupelform verwenden mit None als Dateinamen, so dass die filename="..." wird für diese Teile aus der Anfrage gestrichen:

>>> 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 kann auch eine Liste von Tupeln mit zwei Werten sein, wenn Sie eine Reihenfolge und/oder mehrere Felder mit demselben Namen benötigen:

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

Wenn Sie beides angeben files y data dann hängt es von der valeur de data was für die Erstellung des POST-Bodys verwendet wird. Wenn data eine Zeichenkette ist, wird nur diese verwendet; andernfalls werden beide data y files verwendet werden, wobei die Elemente in data zuerst aufgeführt.

Außerdem gibt es die ausgezeichnete requests-toolbelt Projekt, das Folgendes umfasst erweiterte Multipart-Unterstützung . Es nimmt Felddefinitionen im gleichen Format wie das Programm files Parameter, aber im Gegensatz zu requests wird standardmäßig kein Parameter für den Dateinamen gesetzt. Darüber hinaus kann es die Anforderung von offenen Dateiobjekten streamen, wobei requests baut zunächst den Anfragekörper im Speicher auf:

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

Felder folgen den gleichen Konventionen; verwenden Sie ein Tupel mit 2 bis 4 Elementen, um einen Dateinamen, einen Teil des Mime-Typs oder zusätzliche Kopfzeilen hinzuzufügen. Anders als die files Parameter wird nicht versucht, einen Standard filename Wert, wenn Sie kein Tupel verwenden.

142voto

runejuhl Punkte 1917

Die Anforderungen haben sich geändert, seit einige der vorherigen Antworten geschrieben wurden. Werfen Sie einen Blick auf dieses Issue auf Github für weitere Einzelheiten und dieser Kommentar für ein Beispiel.

Kurz gesagt, die files ein Wörterbuch, wobei der Schlüssel der Name des Formularfeldes und der Wert entweder eine Zeichenkette oder ein Tupel der Länge 2, 3 oder 4 ist, wie im Abschnitt POST einer Multipart-kodierten Datei im Schnellstart für Anfragen:

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

In der obigen Darstellung ist das Tupel wie folgt zusammengesetzt:

(filename, data, content_type, headers)

Wenn der Wert nur eine Zeichenkette ist, ist der Dateiname derselbe wie der Schlüssel, wie im Folgenden dargestellt:

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

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

72c2b6f406cdabd578c5fd7598557c52

Wenn der Wert ein Tupel ist und der erste Eintrag lautet None wird die Eigenschaft filename nicht berücksichtigt:

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

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

72c2b6f406cdabd578c5fd7598557c52

136voto

ccpizza Punkte 24937

Sie müssen die files Parameter zum Senden einer mehrteiligen POST-Anfrage sogar wenn Sie keine Dateien hochladen müssen.

Vom Original Anfragen Quelle:

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.

Der relevante Teil lautet: file-tuple can be a :

  • 2-tuple ( Dateiname, fileobj )
  • 3-tuple ( filename, fileobj, content_type )
  • 4-tuple ( filename, fileobj, content_type, custom_headers ).

Was vielleicht nicht offensichtlich ist, ist, dass fileobj kann sein entweder ein tatsächliches Dateiobjekt beim Umgang mit Dateien, OR eine Zeichenkette, wenn es sich um reine Textfelder handelt.

Die einfachste mehrteilige Formularanforderung, die sowohl hochzuladende Dateien als auch Formularfelder enthält, sieht wie folgt aus:

import requests

multipart_form_data = {
    'upload': ('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)

Beachten Sie die None als erstes Argument im Tupel für reine Textfelder - dies ist ein Platzhalter für das Feld filename, das nur für Datei-Uploads verwendet wird, aber für Textfelder, die None als erster Parameter ist erforderlich, damit die Daten übermittelt werden können.

Mehrere Felder mit demselben Namen

Wenn Sie mehrere Felder mit demselben Namen buchen müssen, können Sie anstelle eines Wörterbuchs Ihre Nutzlast als Liste (oder Tupel) von Tupeln definieren:

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

Streaming-Anfragen API

Wenn Ihnen die obige API nicht pythonisch genug ist, dann sollten Sie die Verwendung von Anfragen Werkzeuggürtel ( pip install requests_toolbelt ), die eine Erweiterung des zentrale Anliegen Modul, das Unterstützung für das Hochladen von Dateien bietet, sowie das MultipartEncoder die anstelle von files und können die Nutzlast auch als Wörterbuch, Tupel oder Liste definieren.

MultipartEncoder kann sowohl für mehrteilige Anfragen mit oder ohne tatsächliche Upload-Felder verwendet werden. Sie muss dem data Parameter.

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

Wenn Sie mehrere Felder mit demselben Namen senden müssen oder wenn die Reihenfolge der Formularfelder wichtig ist, können Sie statt eines Wörterbuchs ein Tupel oder eine Liste verwenden:

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

30voto

Jainik Punkte 2233

Hier ein einfaches Code-Snippet zum Hochladen einer einzelnen Datei mit zusätzlichen Parametern unter Verwendung von Anfragen:

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)

Bitte beachten Sie, dass Sie nicht explizit einen Inhaltstyp angeben müssen.

HINWEIS: Ich wollte eine der obigen Antworten kommentieren, konnte es aber wegen des geringen Ansehens nicht, also habe ich hier eine neue Antwort verfasst.

12voto

Chris Punkte 5850

Durch die Angabe einer files Parameter in der POST Anfrage, die Content-Type der Anfrage ist automatisch eingestellt auf multipart/form-data (gefolgt von der boundary Zeichenfolge, die zur Trennung der einzelnen Körperteile in der mehrteiligen Nutzlast verwendet wird), ob Sie nur files , oder form-data y files zur gleichen Zeit (also eine sollte nicht versuchen, die Content-Type manuell in diesem Fall). Wenn hingegen nur form-data gesendet wurden, die Content-Type würde automatisch auf application/x-www-form-urlencoded .

Sie können die Content-Type Header der Anfrage, um dies anhand des folgenden Beispiels zu überprüfen, das zeigt, wie man mehrere Dateien (oder eine einzige Datei) mit (optional) demselben key (d.h., 'files' im folgenden Fall), sowie mit optionalen form-data (d.h., data=data im untenstehenden Beispiel). Die Dokumentation über die POST einfach und mehrfach files kann gefunden werden aquí y aquí . Falls Sie große Dateien hochladen müssen, ohne sie in den Speicher zu lesen, sehen Sie sich folgende Seiten an Streaming-Uploads . Für die Serverseite - falls diese benötigt wird - schauen Sie bitte unter diese Antwort , aus dem der nachstehende Codeausschnitt stammt, und der die FastAPI Web-Framework.

Ejemplo

import requests

url = 'http://127.0.0.1:8000/submit'
files = [('files', open('a.txt', 'rb')), ('files', open('b.txt', 'rb'))]
#file = {'file': open('a.txt','rb')} # to send a single file
data ={"name": "foo", "point": 0.13, "is_accepted": False}
r = requests.post(url=url, data=data, files=files) 
print(r.json())
print(r.request.headers['content-type'])

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X