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

Drive API调用SQL存储的pageToken返回400 INVALID_VALUE错误

问题现象
  • 部署了读取Google Drive指定文件夹数据的程序,将每次接口返回的最新pageToken存储至SQL数据库,循环拉取文件夹内总量超25万的条目
  • 程序支持两种运行模式:
    • 全量拉取模式:从头启动,迭代调用接口返回的nextPageToken完成全量数据拉取
    • 断点续拉模式:读取SQL表中存储的上一次nextPageToken值,从断点位置继续执行拉取逻辑
  • 异常表现:单次运行不依赖SQL存储的token、直接连续迭代接口返回的token时,程序可正常运行直到所有数据读取完成;但读取SQL中存储的token发起接口调用时,会返回400错误请求。该问题在为请求增加modifiedTime时间范围过滤条件后出现,增加过滤条件前断点续拉流程运行正常。
问题代码
def get_video_items(folder, service, token=None, begin=None, to=None):
    # EXTRACT ALL ITEMS FROM SPECIFIED FOLDER
    if not token:
        size = PAGESIZE_2
        response = service.files().list(q="'" + folder[0]['id'] + "' in parents and modifiedTime > '" + begin + "'and"
                                        "modifiedTime < '" + to + "'",
                                        orderBy='modifiedTime asc',
                                        pageSize=size, fields='nextPageToken, files(id, name, webViewLink)').execute()

    else:
        size = PAGESIZE_2
        response = service.files().list(q="'" + folder[0]['id'] + "' in parents and modifiedTime > '" + begin + "'and"
                                        "modifiedTime < '" + to + "'",
                                        orderBy='modifiedTime asc',
                                        pageSize=size, fields='nextPageToken, files(id, name, webViewLink)',
                                        pageToken=token).execute()

    videos = response.get('files', [])
    # RECORD PAGE TOKEN TO CALL NEXT PAGE
    if len(videos) < size:
        videos = None
    else:
        token = response.get('nextPageToken', None)
    return videos, token 
报错信息

raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 400 when requesting https://www.googleapis.com/drive/v3/files?q=************+in+parents+and+modifiedTime+%3E+%272019-07-01T16%3A48%3A13.212522%27andmodifiedTime+%3C+%272022-07-01T10%3A38%3A13.212491%27&orderBy=modifiedTime+asc&pageSize=50&fields=nextPageToken%2C+files%28id%2C+name%2C+webViewLink%29&pageToken=%21%21AI9FV7Qu_zWbHy0zBnPqDegUffr0l2XrKvi_U_M8W17yWJ59skMILpiAR-0Oc_i-ZDxrj8Jx3AXBaMrqUh9fnrtxDcBEn4qKTHsdm4Y4_rnc6SJItgaXmOfx7HVjszfmG5z-bOHU-7flG0EIP1a2HIXq-GWFffu2ykGUaDACnteguHEjAe-tNEmPnRR1tHNmNEJACFgWnTnoCXb0w2M9kjA_VXGLwMtQ1Ar-Y9A6IJH3wQXxrIqVrIlWfJ-cEu0z6zGv3saM942Y4O0SKzLyTXk8-SWIozKH5pcFfeBTtn1z_OySgl-lHNIxRgu77lRy9wmgBhCAki8GHL0YjB_QXq7sodq7uwc9VPadokI7__0uoDCjAnvPkQ7gjH7VQWI80a1DadQ0GrZ3&alt=json returned "Invalid Value". Details: "[{'domain': 'global', 'reason': 'invalid', 'message': 'Invalid Value', 'locationType': 'parameter', 'location': 'pageToken'}]"

问题根因
  1. 查询语句存在语法错误:原代码拼接q参数时,begin时间字符串后直接拼接and关键字,缺少空格,最终拼接出的查询条件为modifiedTime > 'xxx'andmodifiedTime < 'yyy',属于非法语法,会触发接口参数校验异常。
  2. pageToken绑定规则不匹配:Google Drive API返回的pageToken是和生成时的请求上下文强绑定的临时凭据,仅支持在完全相同的请求参数下使用,以下任意情况都会触发pageToken无效报错:
    • 续拉时传入的q过滤规则、pageSize、orderBy、fields任意一项参数,和生成该token时的参数不一致(本次新增modifiedTime过滤条件后,使用旧逻辑生成的历史token发起请求,必然触发报错)
    • SQL存储字段长度不足:Google Drive返回的pageToken长度普遍超过255字符,如果存储字段设置为varchar(255),超长部分会被截断,从库中读取的token是不完整的
    • 读写过程发生值篡改:存储或读取token时做了额外的URL编解码、特殊字符转义操作,导致传入接口的token值和接口原始返回值不一致
  3. 分页判断逻辑存在bug:原代码通过「本次返回条目数是否小于pageSize」判断是否还有下一页,但实际接口可能在返回条目数等于pageSize时已经没有下一页(此时nextPageToken返回None),原逻辑会错误覆盖token值,甚至把None存入数据库,导致续拉逻辑异常。
可行解决方案
  • 修复查询语句拼接语法:在and关键字前后补全空格,避免非法查询语法,推荐使用f-string拼接减少格式错误:
    q = f"'{folder[0]['id']}' in parents and modifiedTime > '{begin}' and modifiedTime < '{to}'"
    
  • 调整SQL存储配置:将存储pageToken的字段类型设置为TEXT或长度不小于1024的VARCHAR,避免长token被截断;读写token时不做任何额外的转义、编解码处理,保证存入和取出的值和接口原始返回值完全一致。
  • 增加参数一致性校验:存储token时,同步把生成该token时用到的所有请求参数(文件夹ID、begin时间、to时间、pageSize、orderBy、fields配置)和token存在同一条记录中;续拉读取token时,先校验当前请求参数和存储的参数是否完全一致,不一致则废弃旧token从头拉取,禁止跨参数场景复用旧token。新增过滤条件后,所有历史生成的旧token直接废弃,不要继续使用。
  • 修复分页判断逻辑:放弃用返回条目数判断分页状态,直接以接口返回的nextPageToken是否存在作为分页终止依据,修正后的代码逻辑如下:
    def get_video_items(folder, service, token=None, begin=None, to=None):
        size = PAGESIZE_2
        q = f"'{folder[0]['id']}' in parents and modifiedTime > '{begin}' and modifiedTime < '{to}'"
        base_params = {
            "q": q,
            "orderBy": "modifiedTime asc",
            "pageSize": size,
            "fields": "nextPageToken, files(id, name, webViewLink)"
        }
        if token:
            base_params["pageToken"] = token
        response = service.files().list(**base_params).execute()
        videos = response.get('files', [])
        next_token = response.get('nextPageToken', None)
        return videos, next_token
    
    循环调用时只要返回的next_token不为None就继续拉取,为None则直接结束分页流程。

内容的提问来源于stack exchange,提问作者Kartikey Mehrotra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 00:18:15