使用Python Requests在单个请求中发送文件和JSON

17 投票
2 回答
23949 浏览
提问于 2025-04-18 12:34

我正在尝试向一个API发送数据(这个API是用SlimPHP构建的),它需要接收一张图片和一些额外的图片元数据,这些元数据是以JSON格式发送的。

我已经用一个REST客户端工具验证过这个API,它工作得很好,我可以成功地向这个服务发送数据,所有的数据都被正确存储。

现在我想用Python来发送数据,但我的JSON数据似乎没有被保存。

我的代码:

    data = {'key1': 'value1', 'key2': 'value2'}
    url = 'http://mydomain.com/api/endpoint'
    headers = {'Authorization': 'my-api-key'}
    files = {'file': (FILE, open(PATH, 'rb'), 'image/jpg', {'Expires': '0'})}
    r = requests.post(url, files=files, headers=headers, data=data)

--

我尝试设置一些额外的请求头,

比如:

headers = {'Authorization': 'unique-auth-key', 'Content-type': 'multipart/form-data'}

或者

headers = {'Authorization': 'unique-auth-key', 'Content-type': 'application/json'}

但是这些尝试导致了500错误。


更新于2014年7月14日:

我使用一个Chrome扩展(高级REST客户端)成功发送了POST请求——这是控制台显示的负载内容:

------WebKitFormBoundarysBpiwrA3hnGPUbMA
Content-Disposition: form-data; name="data"
test
------WebKitFormBoundarysBpiwrA3hnGPUbMA
Content-Disposition: form-data; name="file"; filename="image.jpg"
Content-Type: image/jpeg
------WebKitFormBoundarysBpiwrA3hnGPUbMA--

我不太确定这意味着什么……

2 个回答

0

如果你需要在一次请求中发送嵌套的JSON数据文件,那么你需要使用json.dumps来处理这些内容(下面的例子中是列表和映射)

import requests

session = requests.Session()
session.post(login_url, json=your_credentials_dict)  # auth , not required
mapping = {"email": "Email", "First Name": "first_name"}
post_data = dict(
   mode=1,
   lists=json.dumps(["133", "899", "911"]),
   mapping=json.dumps(mapping),
)
files_data = {'file': ('your-file-name.csv', file_bytes_content)}
response = session.post(<your-url>, data=post_data, files=files_data)

最终发送的请求会是这样的:

********** post file data with mapping **********
 --> REQUEST: POST /v3/contacts/import/file/ HTTP/1.1
 --> REQUEST: Host: <your-host>:4000
 --> REQUEST: User-Agent: python-requests/2.23.0
 --> REQUEST: Accept-Encoding: gzip, deflate
 --> REQUEST: Accept: */*
 --> REQUEST: Connection: keep-alive
 --> REQUEST: Cookie: jwt=eyJ0e***
 --> REQUEST: Content-Length: 654
 --> REQUEST: Content-Type: multipart/form-data; boundary=db850c3230988e010b1ebe21be3fb344
 --> REQUEST: 
 --> REQUEST: --db850c3230988e010b1ebe21be3fb344
Content-Disposition: form-data; name="lists"

["7syewz9qUz0CbaqhaGm", "LBfWKJwq4Q"]
--db850c3230988e010b1ebe21be3fb344
Content-Disposition: form-data; name="mode"

2
--db850c3230988e010b1ebe21be3fb344
Content-Disposition: form-data; name="mapping"

{"email": "Email", "First Name": "first_name"}
--db850c3230988e010b1ebe21be3fb344
Content-Disposition: form-data; name="file"; filename="good.csv"

first_name;last_name;email;age
John;Doe;test.testson_1@test.com;50
Mark;Twen;test.testson_2@test.com;20

--db850c3230988e010b1ebe21be3fb344--

这里使用了requests-toolbelt这个包来生成上面的日志,详细信息可以查看这个链接:https://pypi.org/project/requests-toolbelt/

22

你的问题在于,你把图片的元数据当作要发送的键值对的来源。其实应该把它作为这些键值对中的一个值来发送。

下面的代码会发送一个请求,和你提供的curl语句很相似:

url = 'my-url.com/api/endpoint'
headers = {'Authorization': 'my-api-key'}
image_metadata = {'key1': 'value1', 'key2': 'value2'}
data = {'name': 'image.jpg', 'data': json.dumps(image_metadata)}
files = {'file': (FILE, open(PATH, 'rb'), 'image/jpg', {'Expires': '0'})}
r = requests.post(url, files=files, headers=headers, data=data)

撰写回答