Pythonのリクエストで「multipart / form-data」を送信する方法は?


213

multipart/form-dataPythonでリクエストを送信する方法は?ファイルの送り方はわかりますが、この方法でフォームデータを送る方法はわかりません。


あなたの質問は本当に明確ではありません。何を達成したいですか?フォームにファイルをアップロードせずに「multipart / form-data」を送信しますか?
Hans Then

4
filesパラメータが両方を行うために使用されるという事実は、非常に悪いAPIです。マルチパートデータの送信というタイトルの問題を提起しました-これを修正するにはより良いAPI必要ですfilesパラメータを使用してマルチパートデータを送信することはせいぜい誤解を招くだけであることに同意する場合は、上記の問題のAPIを変更するよう依頼してください。
Piotr Dobrogost

@PiotrDobrogostその問題はクローズされています。関連性があるかどうかにかかわらず、クローズドな問題についてコメントするように人々に勧めないでください。
Ian Stapleton Cordasco、2013

1
気にしないで、私はあなたのコメントが閉じられる前に投稿されたことに気づきました。StackOverflowが物事を時系列で保持しないのが嫌いです。
Ian Stapleton Cordasco、2013

回答:


168

基本的に、filesパラメーター(辞書)を指定するrequestsと、はmultipart/form-dataPOSTではなくPOST を送信しますapplication/x-www-form-urlencoded。ただし、その辞書の実際のファイルを使用することに限定されません。

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

また、httpbin.orgでは、どのヘッダーを使用して投稿したかを知ることができます。で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'}

さらに、単一の文字列またはバイトオブジェクトの代わりにタプルを使用することにより、各部分のファイル名、コンテンツタイプ、および追加のヘッダーをさらに制御できます。タプルには2〜4個の要素が含まれることが期待されます。ファイル名、コンテンツ、オプションでコンテンツタイプ、および追加のヘッダーのオプションの辞書。

Noneファイル名としてを含むタプルフォームを使用して、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 順序付けや同じ名前の複数のフィールドが必要な場合は、2つの値のタプルのリストにすることもできます。

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

あなたは両方を指定した場合filesdata、それが依存するdataPOSTボディを作成するために使用されるもの。dataが文字列の場合、それだけが使用されます。そうでない場合の両方datafilesの要素と、使用されていますdataます。

高度なマルチパートサポートrequests-toolbeltを含む優れたプロジェクトもあります。パラメータと同じ形式でフィールド定義を受け取りますが、とは異なり、デフォルトではファイル名パラメータを設定しません。さらに、開いているファイルオブジェクトからリクエストをストリーミングできます。この場合、最初にメモリ内にリクエスト本文が作成されます。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}
)

フィールドは同じ規則に従います。2〜4要素のタプルを使用して、ファイル名、パートMIMEタイプ、または追加のヘッダーを追加します。filesパラメーターとは異なり、filenameタプルを使用しない場合、デフォルト値を見つける試みは行われません。


3
files = {}を使用する場合、headers = {'Content-Type': 'blah blah'}は使用しないでください。
崎08

5
@zaki:実際、multipart/form-dataContent-Typeには、投稿の本文のパーツを非正規化するために使用される境界値を含める必要があるためです。Content-Typeヘッダーを設定しないと、ヘッダーrequestsが正しい値に設定されます。
Martijn Pieters

重要な注意:リクエストはmultipart/form-dataの値files=が真実であるかのようにのみ送信されるため、multipart/form-dataリクエストを送信する必要があるがファイルを含まない場合は、などの真実で意味のない値{'':''}を設定data=し、リクエスト本文で設定できます。これを行う場合は、Content-Type自分でヘッダーを指定しないでください。requestsあなたのためにそれを設定します。あなたはここに真実のチェックを見ることができます:github.com/psf/requests/blob/...
ダニエルSitunayake

@DanielSitunayakeそのようなハックの必要はありません。辞書にすべてのフィールドを置くだけでfiles、それらはファイルである必要はありません(タプル形式を使用し、ファイル名をに設定することを確認してくださいNone)。requests_toolbeltプロジェクトを使用してください。
Martijn Pieters

@MartijnPietersに感謝します。タプル形式のトリックは素晴らしいです!それを試してみます。
Daniel Situnayake

107

以前の回答が書かれてから、リクエストが変更されました。詳細についてはGithubバグスレッドを確認し、例についてこのコメントを参照してください。

つまり、filesパラメータはa dictを取り、キーはフォームフィールドの名前、値は文字列または2、3、4の長さのタプルのいずれかです。これは、リクエストのマルチパートエンコードファイルのPOSTで説明されています。クイックスタート:

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

上記では、タプルは次のように構成されています。

(filename, data, content_type, headers)

値が単なる文字列の場合、ファイル名は次のようにキーと同じになります。

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

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

72c2b6f406cdabd578c5fd7598557c52

値がタプルで、最初のエントリがNoneファイル名プロパティである場合は含まれません。

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

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

72c2b6f406cdabd578c5fd7598557c52

2
何を区別する必要がある場合namefilenameだけでなく、同じ名前の複数のフィールドがありますか?
Michael

1
@Michaelと同様の問題があります。質問を見て何か提案できますか?[リンク](stackoverflow.com/questions/30683352/…
Shaardool 2015年

誰かが同じ名前の複数のフィールドを持つことでこの問題を解決しましたか?
user3131037

1
filesタプルの最初の値として空の文字列を渡すトリックは機能しなくなりました。requests.post data追加の非ファイルmultipart/form-dataパラメータを送信するには、代わりにパラメータを使用する必要があります
Lucas Cimon

1
None空の文字列の代わりに渡すとうまくいくようです
Alexandre Blin

73

ファイルをアップロードする必要がない場合でもfiles、このパラメーターを使用してマルチパートフォームのPOSTリクエストを送信する必要があります。

元のリクエストソースから:

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.

関連する部分は次のとおりです。 file-tuple can be a2-tuple、。3-tupleor a4-tuple

上記に基づいて、アップロードするファイルとフォームフィールドの両方を含む最も単純なマルチパートフォームリクエストは次のようになります。

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)

Noneプレーンテキストフィールドのタプルの最初の引数として-これは、ファイルのアップロードのために使用されるファイル名フィールドのプレースホルダであるが、テキストフィールドに渡すNone最初のパラメータは、データが送信されるために必要とされます。

同じ名前の複数のフィールド

同じ名前の複数のフィールドを投稿する必要がある場合は、辞書の代わりに、ペイロードをタプルのリスト(またはタプル)として定義できます。

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

ストリーミングリクエストAPI

上記のAPIが十分にpythonicでない場合は、拡張機能であるリクエストツールベルトpip install requests_toolbelt)の使用を検討してください。コア要求だけでなく、ストリーミングファイルアップロードのサポートを提供するモジュールMultipartEncoderの代わりに使用することができfiles、かつこれもできますペイロードは、辞書、タプル、またはリストとして定義します。

MultipartEncoder実際のアップロードフィールドの有無にかかわらず、マルチパートリクエストの両方に使用できます。に割り当てる必要がありますdataパラメータにます。

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

同じ名前の複数のフィールドを送信する必要がある場合、またはフォームフィールドの順序が重要な場合は、辞書の代わりにタプルまたはリストを使用できます。

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

これありがとう。キーの順序は私にとって重要であり、これは非常に役立ちました。
Splendor

すごい。どういうわけか、私が使用しているAPIでは、同じキーに対して2つの異なる値が必要です。これは素晴らしいです。ありがとうございました。
2016年

@ccpizza、実際にこの行は何を意味していますか?> "( 'file.py'、open( 'file.py'、 'rb')、 'text / plain')"。それは私にはうまく
いき

@DenisKoreyba:これは、名前の付いたファイルfile.pyがスクリプトと同じフォルダーにあると想定するファイルアップロードフィールドの例です。
ccpizza

1
None空の文字列の代わりに使用できます。その場合、リクエストにはファイル名がまったく含まれません。だから、代わりにContent-Disposition: form-data; name="action"; filename=""それになりますContent-Disposition: form-data; name="action"。これは、サーバーがこれらのフィールドをファイルではなくフォームフィールドとして受け入れるために私にとって重要でした。
Mitar

9

以下は、リクエストを使用して追加のパラメータを含む単一のファイルをアップロードするための簡単なコードスニペットです。

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)

コンテンツタイプを明示的に指定する必要がないことに注意してください。

注:上記の回答の1つについてコメントしたかったが、評判が低かったためコメントできなかったため、ここで新しい回答を作成しました。


4

nameサイトのHTMLにあるアップロードファイルの属性を使用する必要があります。例:

autocomplete="off" name="image">

分かり name="image">ますか?ファイルをアップロードするサイトのHTMLにあります。これを使用してファイルをアップロードする必要がありますMultipart/form-data

脚本:

import requests

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

ここで、画像の代わりに、アップロードファイルの名前をHTMLで追加します

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

アップロードでアップロードのボタンをクリックする必要がある場合は、次のように使用できます。

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

次にリクエストを開始します

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

そして、ファイルが正常にアップロードされました


3

マルチパート/フォームデータのキーと値を送信する

curlコマンド:

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

python リクエスト-より複雑なPOSTリクエスト

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

multipart / form-dataファイルを送信する

curlコマンド:

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 リクエスト-マルチパートエンコードされたファイルをPOSTします

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

それで全部です。


-1

これは、1つの大きな単一ファイルをマルチパートフォームデータとしてアップロードするために必要なPythonスニペットです。NodeJs Multerミドルウェアがサーバー側で実行されている。

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

:サーバー側の場合multerのドキュメントで確認してくださいhttps://github.com/expressjs/multer のように、1つのファイルを受け入れるために使用され、ここで、単一のフィールド(「フィールド名を」):

var upload = multer().single('fieldName');
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.