如何在python中发送带有请求的“ multipart / form-data”?


211

如何multipart/form-data在python中发送带有请求的请求?我了解如何发送文件,但是如何使用这种方法发送表单数据尚不清楚。


您的问题还不清楚。您想实现什么?您是否希望在不上传表单的情况下发送“ multipart / form-data”?
汉斯,然后

4
files使用参数同时执行这两个操作的事实是非常糟糕的API。我提出了题为“ 发送多部分数据”的问题-我们需要更好的API来解决此问题。如果您同意使用files参数发送mulitpart数据充其量会误导您,请在上述问题中要求更改API。
Piotr Dobrogost 2012年

@PiotrDobrogost,该问题已关闭。不要鼓励人们对已解决的问题(无论是否相关)发表评论。
伊恩·斯台普顿·科尔达斯科

1
没关系,我只是意识到您的评论是在关闭之前发布的。我讨厌StackOverflow不会按时间顺序排列事物。
伊恩·斯台普顿·科尔达斯科

Answers:


166

基本上,如果您指定files参数(字典),requests则将发送multipart/form-dataPOST而不是application/x-www-form-urlencodedPOST。您不限于在该词典中使用实际文件,但是:

>>> 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 如果需要排序和/或具有相同名称的多个字段,也可以是二值元组的列表:

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

如果同时指定filesdata,然后它取决于价值data东西将被用于创建POST体。如果data是字符串,则仅使用它将;否则datafiles都使用和,data首先列出元素。

还有一个出色的requests-toolbelt项目,其中包括高级的Multipart支持。它采用与files参数格式相同的字段定义,但是与不同requests,它默认不设置文件名参数。另外,它可以从打开的文件对象流式传输请求,requests首先将在内存中构造请求主体:

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'}!
扎基

5
@zaki:确实,因为multipart/form-dataContent-Type 必须包含用于定义帖子正文中各部分的边界值。不设置Content-Type标题可确保requests将其设置为正确的值。
马丁·彼得斯

重要说明:仅会在请求的发送方式为真multipart/form-data值的files=情况下发送multipart/form-data请求,因此,如果您需要发送请求但不包含任何文件,则可以设置真值但无意义的值(例如){'':''},并data=在请求正文中进行设置。如果这样做,则不要Content-Type自己提供标题。requests将为您设置。:你可以在这里看到的真相检查github.com/psf/requests/blob/...
丹尼尔Situnayake

@DanielSitunayake无需进行此类破解。只需将所有字段放入filesdict中,它们就不必是文件(只需确保使用元组形式并将文件名设置为None)。更好的是,使用该requests_toolbelt项目。
马丁·彼得斯

感谢@MartijnPieters,使用元组形式的技巧很棒!会尝试一下。
Daniel Situnayake

107

自从编写了先前的答案以来,请求已更改。请查看Github上的bug线程以获取更多详细信息,并以示例的形式发表评论

简而言之,files参数使用a dict作为参数,键是表单字段的名称,值是字符串或2、3或4个长度的元组,如在请求中发布多部分编码文件一节中所述快速开始:

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

如果值是一个元组,并且第一个条目是Nonefilename属性,将不包括在内:

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

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

72c2b6f406cdabd578c5fd7598557c52

2
如果您需要区分namefilename并且有多个具有相同名称的字段怎么办?
迈克尔

1
我有一个类似@@ Michael的问题。您可以看看这个问题并提出一些建议吗?[link](stackoverflow.com/questions/30683352/…
Shaardool

有人用多个具有相同名称的字段解决了这个问题吗?
user3131037 2015年

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对您来说还不够Python,那么请考虑使用request工具带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'),
           )
    )

这次真是万分感谢。按键顺序对我很重要,这很有帮助。
辉煌

惊人。莫名其妙的是,我正在使用的api对于同一密钥需要2个不同的值。这真太了不起了。谢谢。
2016年

@ccpizza,这行实际上意味着什么?>“(('file.py',open('file.py','rb'),'text / plain')”。它对我不起作用:(
Denis Koreyba

@DenisKoreyba:这是一个文件上传字段的示例,它假定名为file.py的文件与脚本位于同一文件夹中。
ccpizza

1
您可以使用None而不是空字符串。然后,请求将根本不包含文件名。所以代替Content-Disposition: form-data; name="action"; filename=""Content-Disposition: form-data; name="action"。对于我来说,这对于服务器将这些字段接受为表单字段而不是文件而言至关重要。
Mitar

8

以下是使用请求上传带有其他参数的单个文件的简单代码段:

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)

请注意,您不需要显式指定任何内容类型。

注意:想对以上答案之一发表评论,但由于声誉不佳而无法发表评论,因此在此处起草了一个新的答复。


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)

发送多部分/表单数据文件

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 请求-发布多部分编码的文件

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

就这样。


-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, 此处字段single('fieldName')用于接受一个文件,如下所示:

var upload = multer().single('fieldName');
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.