Python,App Engine:HTTP多部分POST到Vk.Ads API [英] Python, App Engine: HTTP multipart POST to Vk.Ads API
问题描述
我花了几天的时间尝试搜索示例和文档,也在ruSO上没有解决.因此,我希望可以在enSO上找到解决方案.
I tried to sole this problem myself for a few days searching on examples and documentations, also it wasn't solved on ruSO. So, I hope that solution will be found here, on enSO.
我开发了一项服务,可使用Python和Google App Engine在Vk社交网络上自动创建广告.最初,用于广告的图片会加载到我的服务器上(第1部分),然后有时将它们上传到Vk服务器上(第2.1部分和第2.2 部分).似乎图片已正确加载并存储在我的服务器上(我下载了图片并与原始图片进行了比较-每个字节都是相同的).但是我附上第1部分代码,以防万一.
I develop a service for automatic creation of ads at Vk social network using Python and Google App Engine. Initially, pictures for ads are loaded to my server (part 1), then they are uploaded to Vk server at some time (parts 2.1 and 2.2). It seems that pictures are loaded and stored on my server correctly (I downloaded them and compared with original ones — every byte is the same). But I attach the part 1 code just in case.
首先要将图片上传到Vk.Ads,我需要才能获取URL -这很简单,因此请跳过它.其次,我需要向带有字段file
的链接发送POST请求,其中包含照片的二进制内容(
To upload a picture to Vk.Ads firstly I need to get a URL — this is simple, so skip it. Secondly, I need to send a POST request to this link with field file
with binary content of the photo (API documentation). I created two ways for that (2.1 and 2.2), but both of them returns errcode: 2
which means corrupted file
. To my mind, the problem is about the requests, but I don't exclude the possibility of it files uploading/storage on my server, or some strange work of the API. I'll appreciate any answers and comments.
import webapp2
from google.appengine.ext import ndb
# stores pictures on the server
class Photo(ndb.Model):
name = ndb.StringProperty()
img = ndb.BlobProperty()
@staticmethod
def get(name):
retval = Photo.query(Photo.name == name).get()
return retval
@staticmethod
def create(name, blob):
retval = Photo()
retval.name = name
retval.img = blob
return retval
class PhotosPage(webapp2.RequestHandler):
def get(self):
# general content of the page:
html = '''<form action="/photos" method="post" enctype="multipart/form-data">
<input type="file" name="flimg"/>
<input value="new_pic" name="flname"/>
<input type="submit" value="Upload"/> </form>'''
def post(self):
n = str(self.request.get('flname'))
f = self.request.get('flimg')
p = Photo.get(n)
if p:
p.img = f
else:
p = Photo.create(n, f)
p.put()
2.1.使用 urlfetch POST到API,方法#1 и海报:
2.1. POST to API, approach #1, using urlfetch и poster:
from poster.encode import multipart_encode, MultipartParam
from google.appengine.api import urlfetch
name = 'file'
content = ... # file binary content
where = ... # gotten URL
options = {
'file': MultipartParam(
name=name,
value=content,
filename=name,
filetype='image/png',
filesize=len(content))
}
data, headers = multipart_encode(options)
pocket = "".join(data)
result = urlfetch.fetch(
url=where,
payload=pocket,
method=urlfetch.POST,
headers=headers)
2.2.使用requests
:
POST到API,方法2
2.2. POST to API, approach #2, using requests
:
import requests
name = 'file'
content = ... # file binary content
where = ... # gotten URL
# I also tried without this dict; is it necessary?
data = {
'fileName': name,
'fileSize': len(content),
'description': 'undefined',
}
result = requests.post(where, files={name: StringIO(content)}, data=data)
此外,对于第二种方法,我提取了请求的内容:
In addition, for the second approach I extracted the content of my request:
POST
https://pu.vk.com/c.../upload.php?act=ads_add&mid=...&size=m&rdsn=1&hash_time=...&hash=...&rhash=...&api=1
Content-Length: 15946
Content-Type: multipart/form-data; boundary=b4b260eace4e4a7082a99753b74cf51f
--b4b260eace4e4a7082a99753b74cf51f
Content-Disposition: form-data; name="description"
undefined
--b4b260eace4e4a7082a99753b74cf51f
Content-Disposition: form-data; name="fileSize"
15518
--b4b260eace4e4a7082a99753b74cf51f
Content-Disposition: form-data; name="fileName"
file
--b4b260eace4e4a7082a99753b74cf51f
Content-Disposition: form-data; name="file"; filename="file"
< File binary content >
--b4b260eace4e4a7082a99753b74cf51f--
更新.
由于 SwiftStudier ,我发现了问题的根源:StringIO
和BytesIO
与文件open
的行为不同.如果仅使用open
,则代码可以很好地工作,但不适用于虚拟文件.如何解决?
UPDATE.
Thanks to SwiftStudier, I found the origin of the problem: StringIO
and BytesIO
don't behave identical to file open
. If I use just open
the code works well, but it doesn't with virtual file. How it can be solved?
import requests
from io import BytesIO
with open('path.to/file.png', 'rb') as fin:
content = BytesIO(fin.read())
token = '...'
url = 'https://api.vk.com/method/ads.getUploadURL?access_token=' + token + '&ad_format=2'
upload_url = requests.get(url).json()['response']
post_fields = {
'access_token': token
}
data_fields = {
# This works:
# 'file': open('path.to/file.png', 'rb')
# But this does not:
'file': content
}
response = requests.post(upload_url, data=post_fields, files=data_fields)
print(response.text)
推荐答案
经过大量实验并研究了不同的HTTP请求内容,我发现了错误代码与有效代码之间的唯一区别.它只有大约4个字节:文件名必须包含扩展名. Vk API甚至会忽略Content-Type: image/png
,但需要.png
或类似的文件名.因此,这不起作用:
After a lot of experiments and investigating of different HTTP requests content I found out the only difference between wrong and working code. It was about 4 bytes only: file name MUST contain the extension. Vk API even ignores Content-Type: image/png
, but needs .png
or similar in filename. So, this doesn't work:
requests.post(upload_url, files={
'file': BytesIO('<binary file content>')
})
但是此选项可以正常工作:
But this option works properly:
requests.post(upload_url, files={
'file': ('file.png', BytesIO('<binary file content>'), 'image/png')
})
就像这样,它不适用于GAE:
Just like this one, which is not available for GAE:
requests.post(upload_url, files={
'file': open('/path/to/image.png', 'rb')
})
StringIO
和StringIO
都适合该任务.如前所述,Content-Type
没关系,可以只是multipart/form-data
.
Both StringIO
and StringIO
are appropriate for that task. As mentioned, Content-Type
does not matter, it can be just multipart/form-data
.
这篇关于Python,App Engine:HTTP多部分POST到Vk.Ads API的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!