如何发送“multipart/form-data"使用python中的请求?

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

531550 次浏览

基本上,如果你指定了一个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项目,它包括先进的多部分支持。它以与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值。

自从编写了以前的一些答案之后,请求已经发生了变化。查看这个问题在Github上获取更多细节,并查看这样的评论作为示例。

简而言之,files参数接受一个字典,键为表单字段的名称,值为字符串或长度为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,则filename属性将不包含:

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


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


72c2b6f406cdabd578c5fd7598557c52

当你不需要上传任何文件时,你需要使用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 a:

  • 2-tuple (文件名,fileobj)
  • 3-tuple (文件名,fileobj,内容类型)
  • 4-tuple (文件名,fileobj, content_type, custom_headers)。

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

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

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对你来说不够python化,那么可以考虑使用请求直到 (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'),
)
)

你需要使用网站HTML中上传文件的name属性。例子:

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

这里,在image的位置,用HTML添加上传文件的名称

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

如果上传需要点击按钮进行上传,可以这样使用:

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

然后启动请求

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

完成,文件上传成功

这是你需要上传一个大的单一文件作为多部分formdata的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');

下面是使用请求上传带有附加参数的单个文件的简单代码片段:

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)

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

注:想评论上述答案之一,但不能因为低声誉,所以起草了一个新的回应在这里。

发送multipart/form-data键和值

curl命令:

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

python requests——更复杂的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 requests - POST一个多部分编码的文件:

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

这是所有。

我试图用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 =我们要发送数据的服务器
  • 报头=发送的报头
  • file_data =发送的参数

为了澄清上面的例子,

即使你不需要上传任何文件,你也需要使用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 =

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)

邮差生成的代码文件上传附加的表单字段:

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

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

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)

我都测试过了,两者都很好。

通过在POST请求中指定files参数,请求的Content-TypePOST2设置为multipart/form-data(后面跟随POST3字符串,用于分隔多部分负载中的每个主体部分),无论您只发送files,还是同时发送form数据和files(因此,在本例中,一个POST4 Content-Type POST5)。然而,如果只发送form数据,则Content-Type将自动设置为POST1。

你可以打印出请求的Content-Type头,使用下面的例子来验证上面的内容,这个例子展示了如何上传多个文件(或单个文件),使用(可选的)相同的key(即下面的例子中的'files'),以及可选的form数据(即data=form_data)。关于如何POST单个和多个files的文档分别可以在在这里在这里中找到。如果你需要上传大文件而不将它们读入内存,请查看流媒体上传。 对于服务器端——如果你需要的话——请看看这个答案,下面的代码片段来自于它,它使用了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'))]
#file = {'file': open('test_files/a.txt','rb')} # for sending a single file
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'])
import json
import os
import requests
from requests_toolbelt import MultipartEncoder


AUTH_API_ENDPOINT = "http://localhost:3095/api/auth/login"


def file_upload(path_img, token ):
url = 'http://localhost:3095/api/shopping/product/image'
name_img = os.path.basename(path_img)


mp_encoder = MultipartEncoder(
fields={
'email': 'mcm9@gmail.com',
'source': 'tmall',
'productId': 'product_0001',
'image': (name_img, open(path_img, 'rb'), 'multipart/form-data')
#'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
}
)


head = {'Authorization': 'Bearer  {}'.format(token),
'Content-Type': mp_encoder.content_type}


with requests.Session() as s:
result = s.post(url, data=mp_encoder, headers=head)


return result


def do_auth(username, password, url=AUTH_API_ENDPOINT):
data = {
"email": username,
"password": password
}


# sending post request and saving response as response object
r = requests.post(url=url, data=data)


# extracting response text
response_text = r.text


d = json.loads(response_text)
# print(d)


return d




if __name__ == '__main__':
result = do_auth('mcm4@gmail.com','123456')
token = result.get('data').get('payload').get('token')
print(token)
result = file_upload('/home/mcm/Pictures/1234.png',token)
print(result.json())