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

Posted

技术标签:

【中文标题】如何在 python 中发送带有请求的“multipart/form-data”?【英文标题】:How to send a "multipart/form-data" with requests in python? 【发布时间】:2012-09-05 07:07:01 【问题描述】:

如何在python中发送multipart/form-datarequests?怎么发送文件,我懂了,但是怎么用这种方法发送表单数据就看不懂了。

【问题讨论】:

你的问题不是很清楚。你想达到什么目标?您是否希望发送“multipart/form-data”而不在表单中上传文件? 查看这个答案***.com/a/64586578/8826047边界很重要! 【参考方案1】:

通过在POST请求中指定files参数,无论你只发送files,还是@ 987654330@数据和files同时。而如果只发送form 数据,则content-type 设置为application/x-www-form-urlencoded

您可以打印出content-type 标头来验证下面给出的示例中的上述内容,该示例显示了如何上传具有(可选)相同key(即files in下例),以及可选的form 数据(即data=form_data)。对于服务器端——如果你需要的话——请查看this answer,下面的代码 sn-p 取自该代码,它使用了FastAPI web 框架。

import requests

url = 'http://127.0.0.1:8000/submit'
files = [('files', open('test_files/a.txt', 'rb')), ('files', open('test_files/b.txt', 'rb'))]
form_data ="name": "foo", "point": 0.13, "is_accepted": False
resp = requests.post(url=url, data=form_data, files=files) 
print(resp.json())
print(resp.request.headers['content-type'])

【讨论】:

【参考方案2】:

这是在多部分请求中发送文件的一种方式

import requests
headers = "Authorization": "Bearer <token>"
myfile = 'file.txt'
myfile2 = 'file': (myfile, open(myfile, 'rb'),'application/octet-stream')
url = 'https://example.com/path'
r = requests.post(url, files=myfile2, headers=headers,verify=False)
print(r.content)

其他方法

import requests

url = "https://example.com/path"

payload=
files=[
  ('file',('file',open('/path/to/file','rb'),'application/octet-stream'))
]
headers = 
  'Authorization': 'Bearer <token>'


response = requests.request("POST", url, headers=headers, data=payload, files=files)

print(response.text)

我都测试过,都很好。

【讨论】:

“数据”和“文件”有什么区别?【参考方案3】:

在不需要上传任何文件的情况下,甚至都需要使用files参数发送多部分表单POST请求。

来自原requests 来源:

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

2-tuple文件名,文件对象3-tuple文件名、文件对象、内容类型4-tuple文件名、fileobj、content_type、custom_headers)。

☝ 可能不明显的是 fileobj 在处理文件时可以是或者一个实际的文件对象,OR 处理纯文本字段时的字符串。

基于以上内容,包含要上传的文件和表单字段的最简单的多部分表单请求将如下所示:

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)

注意 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,那么请考虑使用 requests toolbelt (pip install requests_toolbelt),它是 core requests 模块的扩展,它提供对文件上传流的支持以及 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 个不同的值用于同一个键。这真太了不起了。谢谢。 @ccpizza,这条线到底是什么意思? >“('file.py',打开('file.py','rb'),'text/plain')”。它对我不起作用:( @DenisKoreyba:这是一个文件上传字段的示例,它假定名为 file.py 的文件与您的脚本位于同一文件夹中。 您可以使用None 代替空字符串。然后请求将根本不包含文件名。所以不是Content-Disposition: form-data; name="action"; filename="",而是Content-Disposition: form-data; name="action"。这对我来说很重要,因为服务器接受这些字段作为表单字段而不是文件。【参考方案4】:

自从写了一些以前的答案后,请求已经发生了变化。查看this Issue on Github 了解更多详情,查看this comment 示例。

简而言之,files 参数采用一个字典,其键是表单字段的名称,值是字符串或 2、3 或 4 长度的元组,如 POST a Multipart-Encoded File 部分所述在请求快速入门中:

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

在上面,元组的组成如下:

(filename, data, content_type, headers)

如果 value 只是一个字符串,则文件名将与 key 相同,如下所示:

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

【讨论】:

如果需要区分namefilename,又需要有多个同名字段怎么办? 我有一个与@Michael 类似的问题。你能看看这个问题并提出一些建议吗? [链接](***.com/questions/30683352/…) 有人用多个同名字段解决了这个问题吗? 将空字符串作为files 元组的第一个值传递的技巧不再起作用:您需要使用requests.post data 参数而不是发送附加的非文件@987654336 @参数 传递 None 而不是空字符串似乎有效【参考方案5】:

Postman 生成的带有附加表单字段的文件上传代码:

import http.client
import mimetypes
from codecs import encode

conn = http.client.HTTPSConnection("data.XXXX.com")
dataList = []
boundary = 'wL36Yn8afVp8Ag7AmP8qZ0SA4n1v9T'
dataList.append(encode('--' + boundary))
dataList.append(encode('Content-Disposition: form-data; name=batchSize;'))

dataList.append(encode('Content-Type: '.format('text/plain')))
dataList.append(encode(''))

dataList.append(encode("1"))
dataList.append(encode('--' + boundary))
dataList.append(encode('Content-Disposition: form-data; name=file; filename=0'.format('FileName-1.json')))

fileType = mimetypes.guess_type('FileName-1.json')[0] or 'application/octet-stream'
dataList.append(encode('Content-Type: '.format(fileType)))
dataList.append(encode(''))

with open('FileName-1.json', 'rb') as f:
  dataList.append(f.read())
dataList.append(encode('--'+boundary+'--'))
dataList.append(encode(''))
body = b'\r\n'.join(dataList)
payload = body
headers = 
  'Cookie': 'XXXXXXXXXXX',
  'Content-type': 'multipart/form-data; boundary='.format(boundary)

conn.request("POST", "/fileupload/uri/XXXX", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))

【讨论】:

上面的答案有点不清楚,但它确实帮助我理解,是使用encode()函数。【参考方案6】:
import requests
# assume sending two files
url = "put ur url here"
f1 = open("file 1 path", 'rb')
f2 = open("file 2 path", 'rb')
response = requests.post(url,files="file1 name": f1, "file2 name":f2)
print(response)

【讨论】:

【参考方案7】:

为了澄清上面给出的例子,

“即使您不需要上传任何文件,您也需要使用 files 参数发送多部分表单 POST 请求。”

文件=

很遗憾,这不起作用。

您需要输入一些虚拟值,例如

files="foo": "bar"

我在尝试将文件上传到 Bitbucket 的 REST API 时遇到了这个问题,为了避免可怕的“不支持的媒体类型”错误,我不得不编写这个可憎的代码:

url = "https://my-bitbucket.com/rest/api/latest/projects/FOO/repos/bar/browse/foobar.txt"
payload = 'branch': 'master', 
           'content': 'text that will appear in my file',
           'message': 'uploading directly from python'
files = "foo": "bar"
response = requests.put(url, data=payload, files=files)

:O=

【讨论】:

不能requests.put(url, files=payload)【参考方案8】:

我正在尝试使用 python 3 中的请求模块向 URL_server 发送请求。 这对我有用:

# -*- coding: utf-8 *-*
import json, requests

URL_SERVER_TO_POST_DATA = "URL_to_send_POST_request"
HEADERS = "Content-Type" : "multipart/form-data;"

def getPointsCC_Function():
  file_data = 
      'var1': (None, "valueOfYourVariable_1"),
      'var2': (None, "valueOfYourVariable_2")
  

  try:
    resElastic = requests.post(URL_GET_BALANCE, files=file_data)
    res = resElastic.json()
  except Exception as e:
    print(e)

  print (json.dumps(res, indent=4, sort_keys=True))

getPointsCC_Function()

地点:

URL_SERVER_TO_POST_DATA = 我们要发送数据的服务器 HEADERS = 已发送标头 file_data = 已发送参数

【讨论】:

【参考方案9】:

发送 multipart/form-data 键和值

卷曲命令:

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

蟒蛇requests - More complicated POST requests:

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

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

卷曲命令:

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

蟒蛇requests - POST a Multipart-Encoded File:

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

就是这样。

【讨论】:

【参考方案10】:

基本上,如果您指定files 参数(字典),那么requests 将发送multipart/form-data POST 而不是application/x-www-form-urlencoded POST。但是,您不仅限于使用该字典中的实际文件:

>>> 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项目,其中包括advanced Multipart support。它采用与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 值。

【讨论】:

如果使用了 files=,则不能使用 headers='Content-Type':'blah blah'! @zaki:确实,因为multipart/form-data Content-Type 必须包含用于划分帖子正文部分的边界值。不设置 Content-Type 标头可确保 requests 将其设置为正确的值。 重要提示:如果files=的值为真,请求只会以multipart/form-data发送,所以如果您需要发送multipart/form-data请求但不包含任何文件,您可以设置一个真实但无意义的值,例如'':'',并在您的请求正文中设置data=。如果您这样做,请不要自己提供 Content-Type 标头; requests 将为您设置。你可以在这里查看真相:github.com/psf/requests/blob/… @DanielSitunayake 不需要这样的黑客攻击。只需将所有字段放在files dict 中,它们不必是文件(只需确保使用元组形式并将文件名设置为None)。更好的是,使用requests_toolbelt 项目。 感谢@MartijnPieters,元组形式的技巧很棒!会试一试的。【参考方案11】:

这里是简单的代码 sn-p 使用请求上传带有附加参数的单个文件:

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)

请注意,您无需明确指定任何内容类型。

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

【讨论】:

最不冗长且最容易理解的。无论如何,文件应该是opened 和'rb' 选项吗? 是的,这是它的核心:filesdata 都作为字典 经过上面许多冗长而复杂的答案,这一个直接进入核心并且有效!【参考方案12】:

这是你需要上传一个大的单个文件作为多部分表单数据的 python sn-p。在服务器端运行 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');

【讨论】:

【参考方案13】:

您需要使用网站 html 中上传文件的 name 属性。示例:

autocomplete="off" name="image">

你看到name="image"&gt;了吗?您可以在用于上传文件的站点的 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)

大功告成,文件上传成功

【讨论】:

以上是关于如何在 python 中发送带有请求的“multipart/form-data”?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过发送带有 application/octet-stream 内容类型的“POST”请求使用 python 将视频上传到 Microsoft 服务

如何使用请求发送带有标头的 PATCH 请求

如何在scrapy中发送带有标头和有效负载的Post请求

如何在 React 中发送带有变量的 POST 请求?

如何在单元测试中使用 JSON 发送请求

redis事务