2

我花了几天时间试图弄清楚如何在 Python 中将图像发布到CloudApp,使用 Requests 来访问CloudApp 的 API。我可以使用使用Poster的pycloudapp来完成此操作,但我想了解如何使用 Requests。

我一直在尝试使用InspectB.in来比较我的脚本和 pycloudapp 发布的内容以尝试找出差异。似乎并不多,但显然存在的少数是重要的。使用我当前的代码,我得到一个服务器端错误 (500),这令人沮丧。因为基于海报的代码有效,我希望找到一种方法让请求也能正常工作,尽管我认为这可能不可行。

CloudApp 使用 Amazon Web Storage,我知道“文件”参数必须与 AWS 一起使用。到目前为止,我已经尝试了几种data = collections.OrderedDict(sorted(upload_values)); data['file'] = open(last_pic, 'rb')不带files参数使用的排列,而不是使用单独的datafiles字典(如建议的这里。我已经files使用和不带文件名的字典。

这是我的代码:

#!/usr/bin/env python

import requests
import os

last_pic = '/.../image.jpg'

USER = 'email@email.com'
PASS = 'mypass'

AUTH_URL = 'http://my.cl.ly'
API_URL = 'http://my.cl.ly/items/new'

s = requests.Session()
s.auth = requests.auth.HTTPDigestAuth(USER, PASS)
s.headers.update({'Accept': 'application/json'})

upload_request = s.get(API_URL)

upload_values = upload_request.json()['params']

filename = os.path.basename(last_pic)
upload_values['key'] = upload_values['key'].replace(r'${filename}', filename)

files = {'file': open(last_pic, 'rb')}

stuff = requests.post(upload_request.json()['url'], data=upload_values, files=files)
print(stuff.text)

根据 InspectB.in,工作(pycloudapp)帖子和我的帖子之间的唯一区别是:

pycloudapp 帖子正文中的每个参数都有Content-Type: text/plain; charset=utf-8,但在我的代码中没有。例如:

--d5e0c013a6de4105b07ac844eea4da6e
Content-Disposition: form-data; name="acl"
Content-Type: text/plain; charset=utf-8

public-read

与我的:

--b1892e959d124887a61143dd2b468579
Content-Disposition: form-data; name="acl"

public-read

文件数据不同。

pycloud应用程序:

--d5e0c013a6de4105b07ac844eea4da6e
Content-Disposition: form-data; name="file"
Content-Type: text/plain; charset=utf-8

����JFIFHH���ICC_PROFILE�applmntrRGB XYZ �...

与我的:

--b1892e959d124887a61143dd2b468579
Content-Disposition: form-data; name="file"; filename="20130608-ScreenShot-180.jpg"
Content-Type: image/jpeg

����JFIFHH���ICC_PROFILE�applmntrRGB XYZ �...

标题基本相同,除了:

pycloud应用程序:

Accept: application/json
Accept-Encoding: identity

矿:

Accept: */*
Accept-Encoding: gzip, deflate, compress

具体来说,两者都成功注册为Content-Type: multipart/form-data

认为接受标头可能是重要的区别,我尝试添加headers = {'accept': 'application/json', 'content-type': 'multipart/form-data'}(以及两个单独的),但没有运气。不幸的是,如果我修改标头,它会覆盖所有标头并丢失多部分编码。

我也想知道文件Content-Type: image/jpeg在我的帖子Content-Type: text/plain; charset=utf-8中还是在工作帖子中可能是问题所在。

为这么长的帖子道歉,这让我发疯了,感谢您提供的任何帮助。

4

1 回答 1

3

几天后,我终于弄清楚了(简单的)问题。CloudApp API 需要对 Amazon 响应中的“Location”标头的“GET”请求。

Pycloudapp工作正常,因为它正确地验证了 GET 响应return json.load(self.upload_auth_opener.open(request))

我不确定为什么我能够在没有任何身份验证的情况下使用Postman正确发布- 不知何故,它在没有凭据的情况下正确地遵循 GET,即使 CloudApp API 指定遵循重定向需要身份验证

我无法使用 Requests 正确跟踪重定向,因为我发布了未经身份验证的值(如果我继续使用 s.post 的 Session(),auth 标头会抛出错误,因为 Amazon 不期望它们),因此后续的 GET 是也未经身份验证。磨难的一个非常令人困惑的部分是发布的图像没有出现在我的 CloudApp 帐户中。但是,后来我发现我可以手动将亚马逊回复的“位置”粘贴到浏览器窗口中,然后发布的图像突然出现在我的帐户中。这让我意识到 POST 是不够的;完成该过程需要经过身份验证的 GET。

然后我发现我没有从requests.post.headers. 花了几分钟才发现它响应的是来自重定向的标头(来自它所遵循的 GET 的 500 错误),而不是来自 POST。添加allow_redirects=False后,我可以正确访问亚马逊响应的“位置”标头。我只是将该标头反馈到我经过身份验证的 Session() 中,它终于起作用了。

对这个过程有帮助的另一件事是这个 SO 线程,它教我使用 Requests 进行日志记录

希望这个解释有意义并对其他人有所帮助。在过去的几天里,我当然学到了很多东西。我的代码可能还需要改进,我想用 urllib.quote_plus 做更多的测试,以及我是否需要 UTF-8 编码的东西,但我今天没有上传,所以必须等待。

我当前的代码:

#!/usr/bin/env python

import requests
from collections import OrderedDict
import keyring
import os

last_pic = '/path/to/image.jpg'

USER = 'myemail@email.com'
KEYCHAIN_SERVICE_NAME = 'cloudapp'

# replace with PASS = 'your_password' if you don't use keyring
PASS = keyring.backends.OS_X.Keyring.get_password(KEYCHAIN_SERVICE_NAME, USER)

AUTH_URL = 'http://my.cl.ly'
API_URL = 'http://my.cl.ly/items/new'

s = requests.Session()
s.auth = requests.auth.HTTPDigestAuth(USER, PASS)
s.headers.update({'Accept': 'application/json'})

upload_request = s.get(API_URL)
param_list = []
for key, value in upload_request.json()['params'].items():
    param_list.append((key.encode('utf8'), value.encode('utf8')))
data = OrderedDict(sorted(param_list))

filename = (os.path.basename(last_pic)).encode('utf8')
data['key'] = data['key'].replace(r'${filename}', filename)
files = {'file': (filename, open(last_pic,'rb').read()) }

stuff = requests.post(upload_request.json()['url'], data=data, files=files, allow_redirects=False)

s.get(stuff.headers['Location'])
于 2013-06-17T17:22:24.393 回答