You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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找不到表单文件:

  1. 变量作用域问题:share_form函数无法访问drive_service、form_id和emails变量,这些变量定义在share_form_with_emails函数内部,且share_form_with_emails没有调用share_form,也没有传递必要参数。
  2. 错误的调用时机:代码在定义完share_form后直接调用了share_form(attempt=1),这时候form_id和emails都未初始化,完全是无效调用。
  3. 权限与所有权问题:通过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就不会有权限问题:
    # 替换__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)
    
    注意:这种方式创建的表单会保存在服务账号的Drive中,你可以手动共享给自己的邮箱以便查看。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:33:06