【Py】模拟multipart/form-data请求并指定boundary格式

本文介绍如何使用Python的requests库模拟POST请求上传文件,并详细解释了multipart/form-data格式的构造方法,包括如何指定boundary。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目中需要模拟一个post请求,将文件上传上去,请求的Content-Type为multipart/form-data
关于multipart/form-data可以参考:https://blog.youkuaiyun.com/five3/article/details/7181521

import requests
data = {
    'token': 'Mo3FUXaggKlU7XaMHwkTvKrETUwU0JNqe7cHua09:fN5NwStGwPD7IzYh2OEMqOL8KrA=:eyJmc2l6ZU1pbiI6MSwiZnNpemVMaW1pdCI6NTI0Mjg4MCwiY2FsbGJhY2tCb2R5VHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJjYWxsYmFja0JvZHkiOiJ7XCJrZXlcIjpcIiQoeDpmaWxlS2V5KVwiLFwiaGFzzFwiOlwiJChldGFnKVwiLFwibmFtZVwiOlwiJChmbmFtZSlcIixcInNpemVcIjpcIiQoZnNpemUpXCIsXCJtaW1lXCI6XCIkKG1pbWVUeXBlKVwiLFwiYnVja2V0XCI6XCJqZHktZXhjZWwtaW1wb3J0XCIsXCJ2aXBMaW1pdFNpemVcIjo1MjQyODgwLFwidXBsb2FkZXJcIjpcIjViOGRmZWQ1NWFmZmRiMzVkZDMyZDdiN1wifSIsImNhbGxiYWNrSG9zdCI6Ind3dy5qaWFuZGFveXVuLmNvbSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly93d3cuamlhbmRhb3l1bi5jb20vZmlsZS91cGxvYWQvaW1wb3J0X2NhbGxiYWNrIiwic2F2ZUtleSI6IiQoeDpmaWxlS2V5KSIsImluc2VydE9ubHkiOjEsInNjb3BlIjoiamR5LWV4Y2VsLWltcG9ydCIsImRlYWRsaW5lIjoxNjM1NDc0NDc1fQ==',
    'x:fileKey': '3756f6da-c7e2-4642-9ff6-2891ac118fac',
    'x:uploader':'5b8dfed55affdb35dd32d7b7'
}
files = [
    ("file", ("test.xlsx", b'test', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))]
r = requests.post(url='http://httpbin.org/post', data=data, files=files)

print(r.request.body.decode('utf-8'))
--9422a7ece95b010ba15995d85176b89b
Content-Disposition: form-data; name="token"

Mo3FUXaggKlU7XaMHwkTvKrETUwU0JNqe7cHua09:fN5NwStGwPD7IzYh2OEMqOL8KrA=:eyJmc2l6ZU1pbiI6MSwiZnNpemVMaW1pdCI6NTI0Mjg4MCwiY2FsbGJhY2tCb2R5VHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJjYWxsYmFja0JvZHkiOiJ7XCJrZXlcIjpcIiQoeDpmaWxlS2V5KVwiLFwiaGFzzFwiOlwiJChldGFnKVwiLFwibmFtZVwiOlwiJChmbmFtZSlcIixcInNpemVcIjpcIiQoZnNpemUpXCIsXCJtaW1lXCI6XCIkKG1pbWVUeXBlKVwiLFwiYnVja2V0XCI6XCJqZHktZXhjZWwtaW1wb3J0XCIsXCJ2aXBMaW1pdFNpemVcIjo1MjQyODgwLFwidXBsb2FkZXJcIjpcIjViOGRmZWQ1NWFmZmRiMzVkZDMyZDdiN1wifSIsImNhbGxiYWNrSG9zdCI6Ind3dy5qaWFuZGFveXVuLmNvbSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly93d3cuamlhbmRhb3l1bi5jb20vZmlsZS91cGxvYWQvaW1wb3J0X2NhbGxiYWNrIiwic2F2ZUtleSI6IiQoeDpmaWxlS2V5KSIsImluc2VydE9ubHkiOjEsInNjb3BlIjoiamR5LWV4Y2VsLWltcG9ydCIsImRlYWRsaW5lIjoxNjM1NDc0NDc1fQ==
--9422a7ece95b010ba15995d85176b89b
Content-Disposition: form-data; name="x:fileKey"

3756f6da-c7e2-4642-9ff6-2891ac118fac
--9422a7ece95b010ba15995d85176b89b
Content-Disposition: form-data; name="x:uploader"

5b8dfed55affdb35dd32d7b7
--9422a7ece95b010ba15995d85176b89b
Content-Disposition: form-data; name="file"; filename="test.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

test
--9422a7ece95b010ba15995d85176b89b--

我们发现上面的boundary是随机的,如果想要指定boundary格式,可以有如下两种方式:

------WebKitFormBoundarymeldjkFD0SjDBiWM
Content-Disposition: form-data; name="token"

Mo3FUXaggKlU7XaMHwkTvKrETUwU0JNqe7cHua09:fN5NwStGwPD7IzYh2OEMqOL8KrA=:eyJmc2l6ZU1pbiI6MSwiZnNpemVMaW1pdCI6NTI0Mjg4MCwiY2FsbGJhY2tCb2R5VHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJjYWxsYmFja0JvZHkiOiJ7XCJrZXlcIjpcIiQoeDpmaWxlS2V5KVwiLFwiaGFzzFwiOlwiJChldGFnKVwiLFwibmFtZVwiOlwiJChmbmFtZSlcIixcInNpemVcIjpcIiQoZnNpemUpXCIsXCJtaW1lXCI6XCIkKG1pbWVUeXBlKVwiLFwiYnVja2V0XCI6XCJqZHktZXhjZWwtaW1wb3J0XCIsXCJ2aXBMaW1pdFNpemVcIjo1MjQyODgwLFwidXBsb2FkZXJcIjpcIjViOGRmZWQ1NWFmZmRiMzVkZDMyZDdiN1wifSIsImNhbGxiYWNrSG9zdCI6Ind3dy5qaWFuZGFveXVuLmNvbSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly93d3cuamlhbmRhb3l1bi5jb20vZmlsZS91cGxvYWQvaW1wb3J0X2NhbGxiYWNrIiwic2F2ZUtleSI6IiQoeDpmaWxlS2V5KSIsImluc2VydE9ubHkiOjEsInNjb3BlIjoiamR5LWV4Y2VsLWltcG9ydCIsImRlYWRsaW5lIjoxNjM1NDc0NDc1fQ==
------WebKitFormBoundarymeldjkFD0SjDBiWM
Content-Disposition: form-data; name="x:fileKey"

3756f6da-c7e2-4642-9ff6-2891ac118fac
------WebKitFormBoundarymeldjkFD0SjDBiWM
Content-Disposition: form-data; name="x:uploader"

5b8dfed55affdb35dd32d7b7
------WebKitFormBoundarymeldjkFD0SjDBiWM
Content-Disposition: form-data; name="file"; filename="test.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

test
------WebKitFormBoundarymeldjkFD0SjDBiWM--

encode_multipart_formdata

from urllib3 import encode_multipart_formdata
import random
import string

boundary = '----WebKitFormBoundary'+''.join(random.sample(string.ascii_letters+string.digits,16))
fields = [("token", (None, 'Mo3FUXaggKlU7XaMHwkTvKrETUwU0JNqe7cHua09:fN5NwStGwPD7IzYh2OEMqOL8KrA=:eyJmc2l6ZU1pbiI6MSwiZnNpemVMaW1pdCI6NTI0Mjg4MCwiY2FsbGJhY2tCb2R5VHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJjYWxsYmFja0JvZHkiOiJ7XCJrZXlcIjpcIiQoeDpmaWxlS2V5KVwiLFwiaGFzzFwiOlwiJChldGFnKVwiLFwibmFtZVwiOlwiJChmbmFtZSlcIixcInNpemVcIjpcIiQoZnNpemUpXCIsXCJtaW1lXCI6XCIkKG1pbWVUeXBlKVwiLFwiYnVja2V0XCI6XCJqZHktZXhjZWwtaW1wb3J0XCIsXCJ2aXBMaW1pdFNpemVcIjo1MjQyODgwLFwidXBsb2FkZXJcIjpcIjViOGRmZWQ1NWFmZmRiMzVkZDMyZDdiN1wifSIsImNhbGxiYWNrSG9zdCI6Ind3dy5qaWFuZGFveXVuLmNvbSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly93d3cuamlhbmRhb3l1bi5jb20vZmlsZS91cGxvYWQvaW1wb3J0X2NhbGxiYWNrIiwic2F2ZUtleSI6IiQoeDpmaWxlS2V5KSIsImluc2VydE9ubHkiOjEsInNjb3BlIjoiamR5LWV4Y2VsLWltcG9ydCIsImRlYWRsaW5lIjoxNjM1NDc0NDc1fQ==', None)),("x:fileKey", (None, '3756f6da-c7e2-4642-9ff6-2891ac118fac', None)),("x:uploader", (None, "5b8dfed55affdb35dd32d7b7", None)),("file", ("test.xlsx", b'test', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))]
m = encode_multipart_formdata(fields, boundary=boundary)
print(m[0].decode('utf-8'))

MultipartEncoder

from requests_toolbelt.multipart.encoder import MultipartEncoder
import random
import string

boundary = '----WebKitFormBoundary'+''.join(random.sample(string.ascii_letters+string.digits,16))
fields = [("token", (None, 'Mo3FUXaggKlU7XaMHwkTvKrETUwU0JNqe7cHua09:fN5NwStGwPD7IzYh2OEMqOL8KrA=:eyJmc2l6ZU1pbiI6MSwiZnNpemVMaW1pdCI6NTI0Mjg4MCwiY2FsbGJhY2tCb2R5VHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJjYWxsYmFja0JvZHkiOiJ7XCJrZXlcIjpcIiQoeDpmaWxlS2V5KVwiLFwiaGFzzFwiOlwiJChldGFnKVwiLFwibmFtZVwiOlwiJChmbmFtZSlcIixcInNpemVcIjpcIiQoZnNpemUpXCIsXCJtaW1lXCI6XCIkKG1pbWVUeXBlKVwiLFwiYnVja2V0XCI6XCJqZHktZXhjZWwtaW1wb3J0XCIsXCJ2aXBMaW1pdFNpemVcIjo1MjQyODgwLFwidXBsb2FkZXJcIjpcIjViOGRmZWQ1NWFmZmRiMzVkZDMyZDdiN1wifSIsImNhbGxiYWNrSG9zdCI6Ind3dy5qaWFuZGFveXVuLmNvbSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly93d3cuamlhbmRhb3l1bi5jb20vZmlsZS91cGxvYWQvaW1wb3J0X2NhbGxiYWNrIiwic2F2ZUtleSI6IiQoeDpmaWxlS2V5KSIsImluc2VydE9ubHkiOjEsInNjb3BlIjoiamR5LWV4Y2VsLWltcG9ydCIsImRlYWRsaW5lIjoxNjM1NDc0NDc1fQ==', None)),("x:fileKey", (None, '3756f6da-c7e2-4642-9ff6-2891ac118fac', None)),("x:uploader", (None, "5b8dfed55affdb35dd32d7b7", None)),("file", ("test.xlsx", b'test', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))]
m = MultipartEncoder(fields, boundary=boundary)
print(m.read().decode('utf-8'))

参考:
https://stackoverflow.com/a/47456554/7151777
https://blog.youkuaiyun.com/Enderman_xiaohei/article/details/89421773

<think>嗯,用户遇到了一个关于multipart.py的错误,具体是第1157行报错“Expected boundary character 45 got 191 at index 2”。我需要仔细分析这个问题,找出可能的原因和解决办法。首先,错误信息提示在索引2的位置期望得到ASCII码45(也就是字符'-'),但实际得到了191。ASCII码191对应的是¿这个字符,通常出现在拉丁-1编码或其他编码中,但显然这里不符合预期。 首先,我应该考虑边界值的生成是否正确。在HTTPmultipart/form-data请求中,边界字符串必须以两个减号(--)开头,后面跟着一个唯一的字符串。例如,boundary='host.1.-852461.936831373.130.24813'中的边界应该在实际使用的时候被正确添加双引号或者正确解析。用户可能在生成边界字符串的时候没有正确添加前导的两个减号,或者在解析的时候没有正确处理。 另外,编码问题也是一个可能的原因。如果边界字符串在传输或解析过程中被错误地编码,比如被当作UTF-8或其他编码处理,可能会导致非ASCII字符的出现。例如,原本应该是ASCII的'-'被错误地转换成了其他字符。需要检查生成边界的代码部分,确保边界字符串仅包含ASCII字符,且在传输过程中没有额外的编码处理。 接下来,查看具体的错误发生位置,multipart.py的第1157行。可能需要查看该库的源代码,了解在这一行代码中进行了什么操作。通常,解析multipart数据时,程序会逐字节检查是否符合预期的边界格式。当在索引2的位置发现ASCII 191而不是预期的45时,说明前两个字符可能已经被正确解析(比如两个'-'),但第三个字符不符合预期。这时候需要确认生成的边界字符串是否正确,或者是否存在编码转换的问题。 可能的解决方法包括:手动指定边界值,确保其仅包含ASCII字符,正确添加前导双减号;检查HTTP头的Content-Type是否正确设置,比如是否缺少boundary参数,或者参数值被错误引用;检查数据部分是否在边界字符串前正确添加了'--',以及末尾是否有'--'结尾。此外,更新相关的库到最新版本也可能修复已知的解析问题。 用户提供的引用信息显示了一个Content-Type的例子,其中boundary='host.1.-852461.936831373.130.24813'。虽然这个例子中的边界字符串看起来是有效的,但可能在实际使用中存在格式错误,比如缺少双引号或者空格等问题。需要确保在设置Content-Type头时,boundary参数被正确格式化和转义,避免引入不可见字符或编码错误。 总结来说,解决这个错误需要从边界字符串的生成、编码处理、HTTP头设置以及库的版本等多个方面进行排查。建议用户先手动指定一个简单的边界字符串,排除自动生成可能带来的问题,然后逐步检查各个步骤是否符合标准。</think>### 解决 `multipart.py:1157 Expected boundary character 45 got 191 at index 2` 错误的方法 此错误通常与HTTP `multipart/form-data`请求的边界(`boundary`)格式有关。以下是分步解决方案: --- #### **1. 检查边界字符串的生成规则** - **问题根源**:边界字符串未遵循RFC规范,可能包含非ASCII字符或格式错误。 - **解决方法**: 1. 确保边界字符串以 `--` 开头,例如: ```python boundary = "--my_custom_boundary_123" # 正确格式 ``` 2. 仅使用ASCII字符(字母、数字、部分符号),避免特殊字符或中文。 3. 手动指定边界值,而非依赖自动生成(部分库可能生成不符合规范的边界)。 --- #### **2. 验证HTTP头中的`Content-Type`格式** - **问题场景**:`Content-Type`头未正确声明`boundary`参数或格式错误[^1]。 - **代码示例**: ```python headers = { "Content-Type": f"multipart/form-data; boundary={boundary}" } ``` - 需确保`boundary`值与实际传输数据中的边界一致。 --- #### **3. 检查数据体的格式** - **关键规则**:每个数据部分必须以 `--{boundary}` 开头,结尾以 `--{boundary}--` 表示结束。 - **错误示例**: ```plaintext --my_boundary # 缺少回车或数据格式不完整 Content-Disposition: form-data; name="file" ``` - **正确格式**: ```plaintext --my_boundary\r\n Content-Disposition: form-data; name="file"\r\n\r\n [data]\r\n --my_boundary--\r\n ``` --- #### **4. 排查编码问题** - **现象**:错误信息中的`got 191`对应字节值`0xBF`,即字符`¿`,表明解析时出现非ASCII字符。 - **解决方法**: - 强制使用ASCII编码生成边界字符串: ```python boundary = "--boundary_" + "".join(random.choices(string.ascii_letters, k=10)) ``` - 避免在HTTP头或数据体中使用非ASCII字符。 --- #### **5. 更新或替换依赖库** - **适用场景**:若使用第三方库(如`requests-toolbelt`或自定义解析器),可能存在版本兼容性问题。 - **操作建议**: ```bash pip install --upgrade requests-toolbelt # 更新库 ``` 或改用标准库(如`email`模块)处理multipart数据。 --- #### **完整代码示例** ```python from requests_toolbelt import MultipartEncoder # 手动指定边界 boundary = "----WebKitFormBoundary1234567890" data = MultipartEncoder( fields={"field1": "value1", "file": ("filename", open("file.txt", "rb"))}, boundary=boundary ) headers = {"Content-Type": data.content_type} response = requests.post(url, data=data, headers=headers) ``` --- ### 相关问题 1. 如何调试HTTP请求中的multipart数据格式? 2. 哪些工具可以可视化multipart/form-data的原始数据? 3. 如何在Python中安全地生成唯一边界字符串? [^1]: 引用内容来自用户提供的错误上下文,表明边界参数可能在解析时格式异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值