使用FORM_ID调用Google Drive API时遇文件未找到错误求助
我是一名Python业余开发者,正在为学生开发自动化词汇测试工具。代码中除向指定邮件列表共享Google表单的功能外,其余功能均正常运行。已手动验证Drive ID与Forms ID完全一致,能通过FORM_ID成功添加测试题目,但使用相同FORM_ID调用Google Drive API设置权限时,始终返回如下HttpError 404错误:
An error occurred: <HttpError 404 when requesting https://www.googleapis.com/drive/v3/files/FORM_ID/permissions?alt=json returned "File not found: FORM_ID.". Details: "[{'message': 'File not found: FORM_ID.', 'domain': 'global', 'reason': 'notFound', 'location': 'fileId', 'locationType': 'parameter'}]">
尝试使用创建Google项目的邮箱认证,问题仍存在,完整代码如下:
from __future__ import print_function from google.oauth2 import service_account from googleapiclient.discovery import build from googleapiclient.errors import HttpError from googleapiclient.discovery import build import time def share_form_with_emails(service_account_file, form_id, emails): SCOPES_DRIVE = ["https://www.googleapis.com/auth/drive"] credentials = service_account.Credentials.from_service_account_file(service_account_file, scopes=SCOPES_DRIVE) drive_service = build('drive', 'v3', credentials=credentials) def share_form(attempt): if attempt > 2: print("Failed to share the form after 5 attempts.") return try: for email in emails: user_permission = { 'type': 'user', 'role': 'reader', # sufficient to submit and view the form 'emailAddress': email } drive_service.permissions().create(fileId=form_id, body=user_permission).execute() print(f'Successfully shared form with {len(emails)} email addresses.') except HttpError as error: print(f'An error occurred: {error}') if "File not found" in str(error): print(f"Attempt {attempt}: Retrying in {2 ** attempt} seconds...") time.sleep(2 ** attempt) share_form(attempt + 1) else: return None share_form(attempt=1) def __main__(): import pandas as pd from apiclient import discovery from httplib2 import Http from oauth2client import client, file, tools EMAILS = ['blah.blah@gmail.com'] SCOPES = "https://www.googleapis.com/auth/forms.body" DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1" SERVICE_ACCOUNT_FILE = r'service_account_keys.json' store = file.Storage('token.json') creds = None if not creds or creds.invalid: flow = client.flow_from_clientsecrets(r'credentials.json', SCOPES) creds = tools.run_flow(flow, store) form_service = discovery.build('forms', 'v1', http=creds.authorize( Http()), discoveryServiceUrl=DISCOVERY_DOC, static_discovery=False) file_name = r'output.xlsx' dfs = pd.read_excel(file_name, sheet_name=None) for df in dfs: if df == 'U2W2' or df == 'U2W3': # TODO: Update this each time to avoid duplicates print(f"Setting up Spelling Test: {df}") name_title = f"Spelling Test: {df}" class_name = 'Green Bears' # TODO: Figure out how to share this automatically with list of emails or something NEW_FORM = { "info": { "title": name_title, "documentTitle": name_title, } } result = form_service.forms().create(body=NEW_FORM).execute() # Create the new spelling test form FORM_ID = result['formId'] print("FORM_ID", FORM_ID) share_form_with_emails(SERVICE_ACCOUNT_FILE, FORM_ID, EMAILS) # Share the newly created forms NEW_FORM_EDITED_INFO = { "requests": [ { "updateFormInfo": { "info": { "title": name_title, "description": f"Type the correct word for the given definition. Good luck, {class_name}!", }, "updateMask": "*", } } ] } NEW_FORM_EDITED_SETTINGS = { "requests": [ { "updateSettings": { "settings": { "quizSettings": { "isQuiz": True } }, "updateMask": "*", } } ] } print(f"-- Setting up Form Info: {df}") form_service.forms().batchUpdate(formId=result["formId"], body=NEW_FORM_EDITED_INFO).execute() # Update form info print(f"-- Setting up Form Settings: {df}") form_service.forms().batchUpdate(formId=result["formId"], body=NEW_FORM_EDITED_SETTINGS).execute() # Update form settings # Add definition questions for index, row in dfs[df].iterrows(): # Iterate over each row as each row is a spelling test question print(f"---- Adding question {index + 1} / 20") NEW_QUESTION = { "requests": [{ "createItem": { "item": { "title": f"𝐃𝐞𝐟𝐢𝐧𝐢𝐭𝐢𝐨𝐧: {dfs[df]['DEFINITION'][index]}", "questionItem": { "question": { "required": False, # Students don't have to answer any of the questions "textQuestion": { # Create text question "paragraph": False # False means short text }, "grading": { "pointValue": 1, "correctAnswers": { "answers": [ { "value": dfs[df]['WORD'][index].lower() }, { "value": dfs[df]['WORD'][index].upper() }, { "value": dfs[df]['WORD'][index].title() } ] } } } }, }, "location": { "index": index } } }] } question_setting = form_service.forms().batchUpdate(formId=result["formId"], body=NEW_QUESTION).execute() # Add the question # Add paragraph questions for i in range(5): print(f"---- Adding sentence question {i + 1} / 5") NEW_QUESTION_SENTENCE = { "requests": [{ "createItem": { "item": { "title": "Create an original sentence using any of the above vocabulary words.", "questionItem": { "question": { "required": False, # Students don't have to answer any of the questions "textQuestion": { # Create text question "paragraph": True # False means short text }, "grading": { "pointValue": 1, } } }, }, "location": { "index": i + 20 } } }] } question_setting = form_service.forms().batchUpdate(formId=result["formId"], body=NEW_QUESTION_SENTENCE).execute() # Add the question print("\n\n ~~~~~ FINISHED ~~~~~ \n\n") __main__()
你的代码存在几个关键问题,导致Drive API找不到表单文件:
- 变量作用域问题:
share_form函数无法访问drive_service、form_id和emails变量,这些变量定义在share_form_with_emails函数内部,且share_form_with_emails没有调用share_form,也没有传递必要参数。 - 错误的调用时机:代码在定义完
share_form后直接调用了share_form(attempt=1),这时候form_id和emails都未初始化,完全是无效调用。 - 权限与所有权问题:通过Forms API创建的表单默认归授权的用户(这里是OAuth2的用户)所有,而你使用服务账号调用Drive API,服务账号无法访问不属于它的文件,除非文件被共享给服务账号,或者创建时指定服务账号为所有者。
修复步骤:
1. 修复函数作用域与调用逻辑
重构share_form_with_emails和share_form,让它们共享变量并正确传递参数:
def share_form_with_emails(service_account_file, form_id, emails): SCOPES_DRIVE = ["https://www.googleapis.com/auth/drive"] credentials = service_account.Credentials.from_service_account_file(service_account_file, scopes=SCOPES_DRIVE) drive_service = build('drive', 'v3', credentials=credentials) def share_form(attempt): if attempt > 2: print(f"Failed to share the form after {attempt} attempts.") return try: for email in emails: user_permission = { 'type': 'user', 'role': 'reader', # 查看和提交表单足够 'emailAddress': email } drive_service.permissions().create(fileId=form_id, body=user_permission).execute() print(f'Successfully shared form with {len(emails)} email addresses.') except HttpError as error: print(f'An error occurred: {error}') if "File not found" in str(error): print(f"Attempt {attempt}: Retrying in {2 ** attempt} seconds...") time.sleep(2 ** attempt) share_form(attempt + 1) else: return None share_form(attempt=1)
2. 解决文件所有权/访问问题
有两种方式解决服务账号无法访问表单的问题:
- 方式一:将表单共享给服务账号
在创建表单后,先使用OAuth2的form_service将表单共享给服务账号的邮箱(可以在服务账号密钥文件中找到client_email字段),然后再调用服务账号的Drive API设置权限。 - 方式二:直接用服务账号创建表单
替换创建表单的授权方式,使用服务账号的凭据调用Forms API,这样表单默认归服务账号所有,后续用同一服务账号调用Drive API就不会有权限问题:
注意:这种方式创建的表单会保存在服务账号的Drive中,你可以手动共享给自己的邮箱以便查看。# 替换__main__中的表单服务初始化代码 credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) form_service = discovery.build('forms', 'v1', credentials=credentials, discoveryServiceUrl=DISCOVERY_DOC, static_discovery=False)
3. 移除无效的提前调用
删除代码中直接调用share_form(attempt=1)的行,这个调用在__main__执行前就会运行,完全没有意义。
其他优化建议
- 错误提示中的重试次数描述不一致(代码中判断
attempt>2却提示5次尝试),建议统一。 - 可以添加权限创建时的
sendNotificationEmail参数,设置为False避免发送默认的共享通知邮件:drive_service.permissions().create( fileId=form_id, body=user_permission, sendNotificationEmail=False ).execute()
内容的提问来源于stack exchange,提问作者Mikitz06

