Google Sheets读取请求配额超限(Error 429)排查求助
报错信息
gspread.exceptions.APIError: {'code': 429, 'message': "Quota exceeded for quota metric 'Read requests' and limit 'Read requests per minute per user' of service 'sheets.googleapis.com' for consumer 'project_number:981210703596'.", 'status': 'RESOURCE_EXHAUSTED', 'details': [{'@type': 'type.googleapis.com/google.rpc.ErrorInfo', 'reason': 'RATE_LIMIT_EXCEEDED', 'domain': 'googleapis.com', 'metadata': {'quota_limit_value': '300', 'consumer': 'projects/981210703596', 'quota_location': 'global', 'service': 'sheets.googleapis.com', 'quota_limit': 'ReadRequestsPerMinutePerUser', 'quota_metric': 'sheets.googleapis.com/read_requests'}}, {'@type': 'type.googleapis.com/google.rpc.Help', 'links': [{'description': 'Request a higher quota limit.', 'url': 'https://cloud.google.com/docs/quota#requesting_higher_quota'}]}]}
执行代码
def agregar_datos_a_sheet(self, poblacion, sustitutos, modulos, fecha_inicio, fecha_final, worksheet, ambiente): # Creamos una lista con los datos a agregar modulos = modulos.split(",") datos_usuarios = [] for i in range(len(poblacion)): datos_usuarios.append( [poblacion[i], '', sustitutos[i], '', fecha_inicio, fecha_final]) if ambiente == 'MercadoP': permisos = Configuration_Manager().get_proxy_ssff()['Permisos_Mercado_P'] else: permisos = Configuration_Manager().get_proxy_ssff()['Permisos_Mercado_D'] #Inserto los IDs de usuarios y fechas worksheet.sheet1.insert_rows(datos_usuarios, row=2) time.sleep(60) for modulo in modulos: if modulo in permisos: columnas_permisos = permisos[modulo] num_usuarios = int(len(poblacion)) update_requests = [] #try: for i in range(2, num_usuarios + 2): for col in columnas_permisos: update_requests.append({ 'updateCells': { 'rows': { 'values': [{ 'userEnteredValue': {'stringValue': 'YES'} }] }, 'fields': 'userEnteredValue', 'start': { 'sheetId': worksheet.sheet1.id, 'rowIndex': i - 1, 'columnIndex': col - 1 } } }) batch_update_requests = {'requests': update_requests} worksheet.batch_update(batch_update_requests) time.sleep(60)
核心问题
- 报错触发在
worksheet.batch_update(batch_update_requests)执行前,而非预期的该行 - 已尝试添加
time.sleep()、替换append.rows()为insert_row()等方法,429配额超限错误仍持续
测试数据
Modulos: ['cm', 'pm'] poblacion : ['30038393', '30034917', '30019948', '30041170', '30024223', '30038393', '30034917', '30019948', '30041170', '30024223', '30038393', '30034917', '30019948', '30041170', '30024223', '30038393', '30034917', '30019948', '30041170', '30024223', '30038393', '30034917', '30019948', '30041170', '30024223'] sustitutos: ['30038393', '30038393', '30038393', '30038393', '30038393', '30034917', '30034917', '30034917', '30034917', '30034917', '30019948', '30019948', '30019948', '30019948', '30019948', '30041170', '30041170', '30041170', '30041170', '30041170', '30024223', '30024223', '30024223', '30024223', '30024223'] fecha_inicio : '01-03-2023 00:00' fecha_final : '11-03-2023 00:00' permisos : {'rc': [11, 15, 18, 21, 29, 33, 36], 'ec': [11, 21, 22, 33, 36], 'pm': [9, 11, 12, 13, 15, 17, 19, 23, 24, 26, 28, 32, 38, 39, 40], 'cm': [10, 14, 16, 20, 22, 33, 36]}
核心原因定位
报错提示读请求每分钟配额超限(300次/分钟),但错误触发在batch_update前,说明:
worksheet.sheet1.insert_rows或循环中重复访问worksheet.sheet1.id可能隐式发起了大量读请求- 当前代码的批量更新逻辑未做合并,导致单批次请求数量过多,连带触发配额消耗
具体优化方案
1. 减少隐式读请求
提前缓存工作表ID,避免循环中多次访问worksheet.sheet1.id(每次访问可能触发读请求):
# 在循环前缓存sheet ID sheet_id = worksheet.sheet1.id # 后续构建update_requests时直接使用缓存的sheet_id 'start': { 'sheetId': sheet_id, 'rowIndex': i - 1, 'columnIndex': col - 1 }
2. 合并批量更新请求
当前代码为每个单元格生成独立的updateCells请求,可合并同行的多列更新,大幅减少请求数量:
update_requests = [] sheet_id = worksheet.sheet1.id # 按行分组处理,合并同一行的多列更新 for i in range(2, num_usuarios + 2): row_updates = [] # 收集该行所有需要设置为YES的单元格值 for _ in columnas_permisos: row_updates.append({'userEnteredValue': {'stringValue': 'YES'}}) # 生成单个覆盖多列的更新请求 update_requests.append({ 'updateCells': { 'rows': {'values': row_updates}, 'fields': 'userEnteredValue', 'start': { 'sheetId': sheet_id, 'rowIndex': i - 1, 'columnIndex': min(columnas_permisos) - 1 # 从该行最左的目标列开始 } } })
3. 动态控制请求速率
替换固定60秒休眠,实现更灵活的速率控制:
- 捕获429错误后,使用响应头中的
Retry-After值进行精准休眠 - 实现指数退避策略(如首次休眠1秒,之后每次翻倍,直到最大阈值)
4. 排查配额消耗点
启用gspread调试日志,明确所有API请求的类型和数量:
import logging logging.basicConfig(level=logging.DEBUG)
通过日志可以定位到哪些操作在消耗读配额,针对性优化。
内容的提问来源于stack exchange,提问作者Cbot

