使用gspread批量提取多Google Sheets文件多工作表指定列数据时如何减少API调用以避免429请求过多错误
先给你揪个代码里的小问题:你在读取工作表数据前居然调用了set_with_dataframe(sheet, df)?这明显是笔误吧?你是要提取数据,怎么反而先写入了?这不仅平白多了一次API调用,还可能意外修改原表格,赶紧把这行删掉!这一步就能直接砍掉每个工作表一半的调用量。
接下来针对你的两个问题逐一给出解决方案:
问题1:有没有一次性获取所有工作表数据的函数?
gspread本身没有直接一键拉取整个Spreadsheet所有工作表指定列的函数,但我们可以利用Google Sheets API的批量请求功能,把多个工作表的读取请求打包成一次API调用——这相当于间接实现了“一次性获取所有数据”的效果,而且能大幅减少调用次数。
问题2:无需暂停即可减少API调用次数的核心方案
下面这些优化能直接把你的API调用量砍到原来的十分之一甚至更低,完全不用靠sleep硬等:
1. 先砍掉所有不必要的调用
除了刚才说的set_with_dataframe,你原代码里每次循环都对df_final做reset_index和fillna,其实完全可以等所有数据都追加完之后再统一处理——虽然这不会减少API调用,但能提升代码效率,避免重复操作。
2. 用批量请求批量读取同一个Spreadsheet下的所有工作表数据
这是最关键的优化!原来你处理一个10工作表的文件要22次调用,现在只需要2次(获取工作表列表+批量读取)。示例代码如下:
from googleapiclient.discovery import build import pandas as pd from operator import itemgetter import gspread as gs from oauth2client.service_account import ServiceAccountCredentials # 初始化认证(和你原代码一致) scope = ["https://spreadsheets.google.com/feeds",'https://www.googleapis.com/auth/spreadsheets',"https://www.googleapis.com/auth/drive.file","https://www.googleapis.com/auth/drive"] creds = ServiceAccountCredentials.from_json_keyfile_name("gspread/service_account.json",scope) client = gs.authorize(creds) # 初始化最终DataFrame df_final = pd.DataFrame() # 获取文件列表,这次保存ID和名称,避免重名问题 file_list = client.list_spreadsheet_files() file_info = [(item['id'], item['name']) for item in file_list] for file_id, file_name in file_info: print(f"Processing file: {file_name}") # 用ID打开文件,比用名称更可靠(避免重名) spreadsheet = client.open_by_key(file_id) # 获取所有工作表列表(1次API调用) worksheet_list = spreadsheet.worksheets() # 构建Google Sheets API服务对象 service = build('sheets', 'v4', credentials=creds) # 准备批量读取的范围:每个工作表取第5、7、9列(对应Google Sheets的F、H、J列),跳过第1行,取前10行(和你原代码逻辑一致) ranges = [] for sheet in worksheet_list[1:]: # 跳过第一个工作表 # 范围格式:工作表名!起始行:结束行,这里取第2到11行(跳过表头,取10行数据),对应F、H、J列 range_str = f"{sheet.title}!F2:F11,H2:H11,J2:J11" ranges.append(range_str) # 执行批量读取(1次API调用,不管多少工作表,只要不超过100个请求都可以打包) response = service.spreadsheets().values().batchGet( spreadsheetId=file_id, ranges=ranges, majorDimension='ROWS' ).execute() # 解析响应并合并到最终DataFrame for idx, sheet_data in enumerate(response['valueRanges']): sheet = worksheet_list[1:][idx] print(f"Processing sheet: {sheet.title}") # 获取工作表数据,空值用空字符串填充 values = sheet_data.get('values', []) # 因为是多列读取,需要把不同列的数据对齐成行 max_rows = max(len(col) for col in zip(*values)) if values else 0 filled_rows = [] for row_num in range(max_rows): row = [] for col in values: row.append(col[row_num] if row_num < len(col) else '') filled_rows.append(row) # 转换成DataFrame df = pd.DataFrame(filled_rows, columns=['column_5', 'column_7', 'column_9']) # 追加到最终DataFrame df_final = pd.concat([df_final, df], ignore_index=True) # 最后统一处理缺失值和索引 df_final.fillna('', inplace=True) print("Final DataFrame:") print(df_final)
3. 优化文件列表的获取逻辑
原代码里你只提取了文件名,然后用client.open(files)——如果有重名文件,这会直接出错。改成保存文件ID,用client.open_by_key(file_id)更可靠,而且调用次数和原来一样。
4. 利用API配额的规则
Google Sheets API给服务账号的配额是每分钟100次请求,用上面的方案,50个文件总共只需要1(获取文件列表) + 50*2(每个文件的工作表列表+批量读取)= 101次请求,完全在配额范围内,根本不会触发429错误。
总结一下优化后的效果:原来处理500个工作表需要大概50*22=1100次API调用,现在只需要101次,直接减少了90%以上的调用量,完全不用靠sleep浪费时间。
内容的提问来源于stack exchange,提问作者Alexandre Barrère

