YouTube数据API v3刷新令牌在发布状态设置为Testing的应用程序上持续过期

2022-01-19 00:16:39 发布

您现在位置:Python中文网/ 问答频道 /正文

我想做什么:

我正试图构建一个Python 3.9程序,使用OAuth2凭据(设置为“测试”发布状态,作为“web应用程序”类型和“外部”用户类型)每天调用YouTube数据API v3,每次进行唯一调用时存储刷新令牌以获得新的访问令牌

我一直在使用YouTube Data API v3 official documentation,来自Google API repository on GitHub的Python代码示例,以及我从Corey Schafer on YouTube找到的OAuth令牌解决方案

到目前为止我所尝试的:

这是我的Python代码(为了匿名起见,我对播放列表ID进行了置乱,但是如果您为一个具有凭据的频道输入自己的播放列表ID,代码运行良好):

# YouTube Data API v3
# Pulling data for the brand account

import os
import pickle
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build

credentials = None


# youtube_data_token_brand.pickle stores the user's credentials from previously successful logins
if os.path.exists('youtube_data_token_brand.pickle'):
    print('Loading Credentials From File...')
    with open('youtube_data_token_brand.pickle', 'rb') as token:
        credentials = pickle.load(token)

# If there are no valid credentials available, then either refresh the token or log in.
if not credentials or not credentials.valid:
    if credentials and credentials.expired and credentials.refresh_token:
        print('Refreshing Access Token...')
        credentials.refresh(Request())
    else:
        print('Fetching New Tokens...')
        flow = InstalledAppFlow.from_client_secrets_file(
            'client_secrets_youtube_data_brand.json',
            scopes=[
                'https://www.googleapis.com/auth/youtube.readonly'
            ]
        )

        flow.run_local_server(port=8080, prompt='consent',
                              authorization_prompt_message='')
        credentials = flow.credentials

        # Save the credentials for the next run
        with open('youtube_data_token_brand.pickle', 'wb') as f:
            print('Saving Credentials for Future Use...')
            pickle.dump(credentials, f)

youtube = build('youtube', 'v3', credentials=credentials)

request = youtube.playlistItems().list(
        part="status, contentDetails", playlistId='UUlG34RnfYmCsNFgxmTmYjPA', maxResults=28
    )

response = request.execute()

for item in response["items"]:
    vid_id = (item["contentDetails"]["videoId"])
    yt_link = f"https://youtu.be/{vid_id}"
    print(yt_link)

我得到的结果:

我的程序运行了大约一周,然后我发现以下错误(同样,为了匿名,我编辑了部分文件路径):

/Users/…/PycharmProjects/GoogleAPIs/HelloYouTubeDataAPIOAuth.py

Loading Credentials From File...

Refreshing Access Token...

Traceback (most recent call last):
  File "/Users/.../PycharmProjects/GoogleAPIs/HelloYouTubeDataAPIOAuth.py", line 23, in <module>
    credentials.refresh(Request())
  File "/Users/.../PycharmProjects/GoogleAPIs/venv/lib/python3.9/site-packages/google/oauth2/credentials.py", line 200, in refresh
    access_token, refresh_token, expiry, grant_response = _client.refresh_grant(
  File "/Users/.../PycharmProjects/GoogleAPIs/venv/lib/python3.9/site-packages/google/oauth2/_client.py", line 248, in refresh_grant
    response_data = _token_endpoint_request(request, token_uri, body)
  File "/Users/.../PycharmProjects/GoogleAPIs/venv/lib/python3.9/site-packages/google/oauth2/_client.py", line 124, in _token_endpoint_request
    _handle_error_response(response_body)
  File "/Users/.../PycharmProjects/GoogleAPIs/venv/lib/python3.9/site-packages/google/oauth2/_client.py", line 60, in _handle_error_response
    raise exceptions.RefreshError(error_details, response_body)
google.auth.exceptions.RefreshError: ('invalid_grant: Bad Request', '{\n  "error": "invalid_grant",\n  "error_description": "Bad Request"\n}')

Process finished with exit code 1

我可以通过从我的目录中删除“youtube\u data\u token\u brand.pickle”文件并重新运行该程序(然后该程序要求我使用我的google帐户登录,并通过OAuth2步骤手动重新授权访问)来绕过这些错误

这让我相信我的刷新令牌即将过期(我发誓我在文档中的某个地方读到,在撤销访问权之前,它不应该过期——我还没有这样做——但在反复搜索之后,我再也找不到它了)

有趣的是,我能够为我控制的另一个YouTube帐户运行相同的程序,而该帐户没有遇到相同的刷新令牌错误问题。我还能够使用相同的token.pickle方法将刷新令牌存储到其他Google API(Google Analytics、YouTube Analytics等)中,但没有遇到任何问题

提前感谢您提供的任何帮助

3条回答
网友
1楼 ·

在阅读了@stvar在其中一个答案中发布的official documentation之后,问题似乎是这个特定的刷新令牌总是有一周的寿命。这只是因为我的处境是一场“完美风暴”:

  1. 问题程序的OAuth2客户端ID凭据是使用Google云控制台创建的(而另一个是使用开发人员控制台创建的)
  2. 问题程序API凭据的OAuth同意屏幕应用程序被设置为“外部用户”类型(两个程序的OAuth2客户端ID凭据实际上都是)
  3. OAuth同意屏幕应用程序的发布状态为“测试”(同样,OAuth2客户端ID凭据也是如此——一个绑定到有问题的程序,另一个运行正常,代码相同,但通过开发人员控制台创建的刷新令牌不同)

唯一的解决方案似乎是发布OAuth同意屏幕应用程序

网友
2楼 ·

根据official documentation,刷新令牌失效的一种情况是与相应刷新令牌关联的应用程序撤销其操作权限

刷新令牌失效的另一种情况是,当相应的帐户已超过授予的最大刷新令牌数时:

There is currently a limit of 50 refresh tokens per Google Account per OAuth 2.0 client ID. If the limit is reached, creating a new refresh token automatically invalidates the oldest refresh token without warning.

你可以检查一下,这确实是发生在account's permission page上的事情

网友
3楼 ·

刷新令牌可能会过期的原因有很多。主要的一点是,每次代码运行时,如果身份验证服务器返回一个新的刷新令牌,而您没有存储它,那么在运行五十次之后,您存储的刷新令牌将过期

注意:身份验证服务器不会每次都返回新的刷新令牌,而是会刷新访问令牌。这似乎是基于C#的一些语言,php没有,我认为node也没有。我还没有找到发生这种情况的原因,我怀疑这是在库中发生的,我不确定python库是否做到了这一点,但无论哪种方式,都最好让它处理事情

看看这段代码,它允许库处理刷新令牌的所有存储。您似乎正在手动执行很多操作。这可能会也可能不会导致您的刷新令牌后悔

"""Hello YouTube API ."""

import argparse

from apiclient.discovery import build
import httplib2
from oauth2client import client
from oauth2client import file
from oauth2client import tools

SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']
CLIENT_SECRETS_PATH = 'client_secrets.json' # Path to client_secrets.json file.



def initialize_youtube():
  """Initializes the youtube service object.

  Returns:
    youtube an authorized youtube service object.
  """
  # Parse command-line arguments.
  parser = argparse.ArgumentParser(
      formatter_class=argparse.RawDescriptionHelpFormatter,
      parents=[tools.argparser])
  flags = parser.parse_args([])

  # Set up a Flow object to be used if we need to authenticate.
  flow = client.flow_from_clientsecrets(
      CLIENT_SECRETS_PATH, scope=SCOPES,
      message=tools.message_if_missing(CLIENT_SECRETS_PATH))

  # Prepare credentials, and authorize HTTP object with them.
  # If the credentials don't exist or are invalid run through the native client
  # flow. The Storage object will ensure that if successful the good
  # credentials will get written back to a file.
  storage = file.Storage('youtube.dat')
  credentials = storage.get()
  if credentials is None or credentials.invalid:
    credentials = tools.run_flow(flow, storage, flags)
  http = credentials.authorize(http=httplib2.Http())

  # Build the service object.
  youtube= build('youtube', 'v3', http=http)

  return youtube

你可能还想确保用户没有通过他们的google帐户撤销你的访问权限,不过我想你已经检查过了

相关问题