无法在服务器端将 google 幻灯片导出为 pdf 格式

Unable to export a google slide as pdf format on server side

这是我在服务器端测试从 google 驱动器导出文件的代码。

import logging

from flask import Flask, render_template, request

from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
from oauth2client.client import AccessTokenCredentials

import httplib2
import io


app = Flask(__name__)


@app.route('/gdrive/selectcallback')
def userselectioncallback():
    print "In user selection callback... "

    code = request.args.get('user_token')
    fileId = request.args.get('fileId')

    credentials = AccessTokenCredentials(code,
                                         'my-user-agent/1.0')


    http = httplib2.Http()
    http_auth = credentials.authorize(http)

    drive_service = build('drive', 'v3', http=http_auth)

    drive_request = drive_service.files().export(
        fileId=fileId,
        mimeType='application/pdf')
    fh = io.FileIO('test.pdf', 'wb')
    downloader = MediaIoBaseDownload(fh, drive_request)
    done = False
    while done is False:
        status, done = downloader.next_chunk()
        print "Download %d%%." % int(status.progress() * 100)

    return code


if __name__ == '__main__':
    # This is used when running locally. Gunicorn is used to run the
    # application on Google App Engine. See entrypoint in app.yaml.
    app.run(host='127.0.0.1', port=8090, debug=True)

在 Web 客户端,一旦用户从文件选择器中选择了一个文件,javascript 前端将使用令牌调用上面 python 代码中的 /gdrive/selectcallback和文件编号。

例如,令牌看起来像这样:ya29.Glu5BG-LQJFqZ-e4uImMSxz-14iS41jVLfXk6rVKvAPjylCwhUh98ZJk1iIC5Eb49pTfflGnU6qE7uzK44AYr0Wn79QMUkF368WFaYrhidrvpVjcsJSZ9P1M8VU6 文件 ID 像这样 1ON9kGyb02TFCygy8jeIYyo2BKj5SzKgAP0xi5Rm08D4

这是相关的前端代码(在 coffeescript 中):

  pickerCallback = () ->
    view   = new google.picker.View(google.picker.ViewId.PRESENTATIONS)
    picker = new google.picker.PickerBuilder()
      .enableFeature(google.picker.Feature.NAV_HIDDEN)
      .setAppId('zeetings')
      .setOAuthToken(oauthToken)
      .addView(view)
      .setDeveloperKey(env['googleapi-client'].apiKey)
      .setCallback(selectCallback)  # The callback calls the python backend
      .build()
    picker.setVisible true

  selectCallback = (data) ->
    if data.action is google.picker.Action.PICKED
      fileId = data.docs[0].id
      fileSelectedCallback(fileId, oauthToken) if fileSelectedCallback

根据调试信息,我的 python 代码发出了这两个 https 调用:

2017-09-01 11:32:38,810 pid 260 tid 140546358265600 INFO
discovery URL being requested: GET https://www.googleapis.com/discovery/v1/apis/drive/v3/rest

2017-09-01 11:32:39,009 pid 260 tid 140546358265600 INFO discovery URL being requested: GET https://www.googleapis.com/drive/v3/files/1ON9kGyb02TFCygy8jeIYyo2BKj5SzKgAP0xi4Rm08D4/export?mimeType=application%2Fpdf

如果我直接在浏览器中使用第二个 url,我会得到以下错误:

{
 "error": {
  "errors": [
   {
    "domain": "usageLimits",
    "reason": "dailyLimitExceededUnreg",
    "message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
    "extendedHelp": "https://code.google.com/apis/console"
   }
  ],
  "code": 403,
  "message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
 }
}

(我不认为上述错误消息实际上反映了根本原因。更有可能是因为调用未在我的浏览器中进行身份验证。)

我怀疑我必须使用 google-auth 库 (https://google-auth.readthedocs.io/en/latest/user-guide.html#making-authenticated-requests),但我不确定如何将 google-auth 与上面的 python 代码结合.我想我可以通过

获得凭证
from google.oauth2 import service_account

credentials = service_account.Credentials.from_service_account_file(
    '/path/to/key.json')

可是这之后的credentials怎么办呢?我要用它来完全取代 credentials = AccessTokenCredentials(code,'my-user-agent/1.0') 吗?

P.S。

根据@Tanaike 的建议,我尝试直接使用 API url。这是我得到的结果:

{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "fileNotDownloadable",
    "message": "Only files with binary content can be downloaded. Use Export with Google Docs files.",
    "locationType": "parameter",
    "location": "alt"
   }
  ],
  "code": 403,
  "message": "Only files with binary content can be downloaded. Use Export with Google Docs files."
 }
}

这似乎是 v3 API 的问题。如果我切换到 v2 并使用 downloadUrl link,我可以下载 pdf 格式的文件。

用户@Tanaike 给了我很多调试这个问题的好建议。我能够直接测试 REST API 以验证 1) 我有正确的访问代码,以及 2) 驱动器 v3 文件导出 API 按预期工作

原来问题出在 MediaIoBaseDownload class 上。如果我从代码中删除它并直接接收数据:

data = drive_service.files().export(
        fileId=fileId,
        mimeType='application/pdf').execute()
f = open('test.pdf)
f.write(data)
f.close()

然后它按预期工作