POST 两个名称相同但类型不同的表单输入

POST two form inputs with same name but different type

我有一个网页在一个表单中提交两个输入。下面可以看到我浏览器的POST
第一个输入是当前上传到站点的文件的路径(type="hidden");第二个输入是要上传的新文件 (type="file").

-----------------------------334620328614915644833547167693
Content-Disposition: form-data; name="image1"

data/file.jpg
-----------------------------334620328614915644833547167693
Content-Disposition: form-data; name="image1"; filename=""
Content-Type: application/octet-stream

这些输入名称相同但类型不同,这导致我对 requests 库的理解出现问题。据我所知,要提交数据,您 POST 一个包含输入名称及其新值的字典。

data = {}

form = get_all_forms(url)[1] #function returns list of forms gained using requests.get() - we select the correct form
formDetails = get_form_details(form) #uses BeautifulSoup to find all input values
for inputTag in formDetails["inputs"]:
  if inputTag["type"] == "file":
    pass #doesn't send anything
  else:
    data[inputTag["name"]] = inputTag["value"] #sets to current value

res = requests.post(url, data=data)

上面的代码将 POST 一个带有 'image1':'data/file.jpg' 的字典。不幸的是,该页面现在认为正在上传一个名为 data/file.jpg 的新文件,当找不到该文件时,它会删除当前图像。

如何 POST 分离 type="file"type="hidden" 的值?

首先,只是一个建议:考虑为您的表单字段使用不同的、更重要的 name 属性(强调不同!)。在你的情况下,这样的事情会更有意义:

<input type="hidden" name="old-file-path" value="data/file.jpg"/>
<input type="file" name="new-file"/>

现在,进入实际问题,您可以使用 requests.post()data=files= 参数来实现您想要的,如下所示:

new_file = open('path/to/new/file.jpg', 'wb')
data     = {'image1': 'data/file.jpg'}
files    = {'image1': new_file}

requests.post(url, data=data, files=files)

应该生成这样的请求:

...
Content-Type: multipart/form-data; boundary=d64ebc3e14a4909699c6f01dd1473855

--d64ebc3e14a4909699c6f01dd1473855
Content-Disposition: form-data; name="image1"

data/file.jpg
--d64ebc3e14a4909699c6f01dd1473855
Content-Disposition: form-data; name="image1"; filename="file.jpg"
...

这里的重点是files=用于上传新文件,data=用于旧文件名。

如果您还想显式设置新文件的名称和 Content-Type,您可以在 files= 字典中传递一个 3 项元组,如下所示:

new_file = open('path/to/new/file.jpg', 'wb')
data     = {'image1': 'data/file.jpg'}
files    = {'image1': ('NEW_FILE_NAME', new_file, 'application/octet-stream')}

requests.post(url, data=data, files=files)

结果:

...
Content-Type: multipart/form-data; boundary=d64ebc3e14a4909699c6f01dd1473855

--d64ebc3e14a4909699c6f01dd1473855
Content-Disposition: form-data; name="image1"

data/file.jpg
--d64ebc3e14a4909699c6f01dd1473855
Content-Disposition: form-data; name="image1"; filename="NEW_FILE_NAME"
Content-Type: application/octet-stream
...

或者如果你想从内存而不是磁盘发送一个文件,甚至是一个空文件,你可以使用:

from io import BytesIO
new_file = BytesIO(b'...file content here...')

# ... same code as above

将其适应您现有的代码,整个事情应该变成:

data = {}
files = {}
new_file = ... # obtain the new file somehow

for inputTag in formDetails["inputs"]:
    if inputTag["type"] == "file":
        files[inputTag["name"]] = ("NEW_FILE_NAME", new_file, "application/octect-stream")
    elif inputTag["type"] == "hidden":
        data[inputTag["name"]] = inputTag["value"]

requests.post(url, data=data, files=files)

最后,在你的赏金评论中,我看到你说:

[...] and when I just need multiple values for the same input name.

在这种情况下,您必须使用 list 而不是 data= 的字典,如下所示:

data = [('image1', 'A'), ('image1', 'B')]
# ...
requests.post(url, data=data)

结果将如下所示:

--9cc0d413be5d2ed2ba8c80a2e7b54442
Content-Disposition: form-data; name="image1"

A
--9cc0d413be5d2ed2ba8c80a2e7b54442
Content-Disposition: form-data; name="image1"

B

这是否有意义取决于您的决定,因为它取决于服务器端的实现以及多次使用相同名称时字段的含义。