R读取Google Sheets的正确姿势:用googlesheets4和OAuth高效获取数据

📅 2026/6/22 13:10:08
R读取Google Sheets的正确姿势:用googlesheets4和OAuth高效获取数据
1. 项目概述为什么读取 Google Sheets 在 R 里不该是场噩梦“Reading Google Sheets In R [the Easy Way]”——这个标题一上来就戳中了太多 R 用户的痛点。我带过三届数据科学训练营每届都有至少三分之一的学员在第一周卡死在这一步想把团队共享的销售日报、用户反馈表、实验记录表直接拉进 R 做分析结果卡在 OAuth 授权、403 错误、read_sheet()找不到工作表、甚至googlesheets4安装失败上。不是他们不会写lm()或ggplot2而是连数据都没法干净地进来。这根本不是 R 的问题而是 Google 的认证机制、R 包的设计逻辑、以及用户对“授权即信任”这一底层原则的误判共同造成的断点。核心关键词R、Google Sheets、googlesheets4、read_sheet、OAuth每一个都不是孤立存在R 是执行环境Google Sheets 是远程数据源googlesheets4是当前唯一被 Google 官方推荐且持续维护的 R 接口包它取代了已归档的googlesheetsread_sheet()是你每天最可能调用的函数而OAuth则是整条链路的守门人——它不是障碍而是护栏。很多人把它当成要绕过去的墙其实它是一道必须亲手打开的门而且门后没有密钥只有你自己的 Google 账户凭证。所谓“the Easy Way”不是跳过 OAuth而是理解它、配合它、让它为你服务而不是替你挡路。这篇文章就是为那些刚打开 RStudio、手边有一份需要分析的 Sheet 链接、却不知道下一步该敲什么命令的人写的。它不假设你懂 API、不预设你配置过.Renviron、也不要求你提前注册 Google Cloud 项目——所有前置动作都会在实操中一步步带你完成包括如何识别一个 Sheet 的真正 ID不是 URL 里的长串乱码、如何避免“OAuth error: request failed with status code 403”这种高频报错、以及为什么read_sheet()默认只读第一个工作表而你真正要的是“Q3-Data”那个标签页。如果你是业务分析师、市场运营、科研助理或者任何需要把协作表格变成可编程数据对象的人这篇就是你的操作手册。它不讲理论只讲今天下午三点前你就能跑通的完整路径。2. 整体设计与思路拆解为什么选 googlesheets4 而不是其他方案2.1 三条路只有一条能走通在 R 里读 Google Sheets历史上有过三条技术路径但如今只有一条是安全、稳定、可持续的老路googlesheets包已归档这是 2014–2019 年的主流方案依赖 Google 的旧版 Sheets API v3 和 Client ID/Secret 认证。2020 年 Google 正式关闭了该 API 的部分端点googlesheets包随之进入归档状态Archived on CRAN。现在安装会报错即使强行从 GitHub 源安装也会在首次授权时触发Error: API call failed: Invalid Credentials。这不是 bug是 Google 主动切断的血管。我试过用devtools::install_github(jennybc/googlesheets)强行回滚结果在 OAuth 流程中卡在https://accounts.google.com/o/oauth2/auth?...页面返回400: invalid_request—— 因为 Google 已不再接受该包生成的 scope 请求。这条路物理意义上已经塌方。弯路gsheet包已弃用这个包试图用更轻量的方式绕过 OAuth通过模拟浏览器请求抓取公开 Sheet 的 CSV 导出链接https://docs.google.com/spreadsheets/d/{SHEET_ID}/export?formatcsv。它只对“任何人可查看”的 Sheet 有效且极易被 Google 的反爬策略拦截。2022 年起大量用户报告gsheet::gsheet_read()返回空数据框或HTTP 429 Too Many Requests。更致命的是它完全无法处理受权限控制的 Sheet比如公司内网共享表而现实中 95% 的业务数据都属于此类。我曾用它批量拉取 20 个部门的周报第 7 个就开始 429重试三次后 IP 被临时限流。这不是工具问题是设计哲学错误把数据接口当网页爬虫用注定短命。正路googlesheets4当前唯一推荐这是 Jenny BryanR 社区公认的 API 交互专家主导开发的包深度绑定 Google 的现代 Sheets API v4并强制采用 OAuth 2.0 授权码流程Authorization Code Flow。它的设计逻辑非常清晰不尝试绕过安全机制而是成为安全机制的一部分。它不存储你的密码不硬编码 token而是每次启动时引导你打开浏览器、登录 Google 账户、明确勾选“允许此应用访问你的 Google Sheets”然后将临时授权码换回一个短期有效的 access token。这个 token 存在本地磁盘默认~/.R/gargle/有自动刷新机制且完全遵循 Google 的 OAuth 最佳实践。2023 年 Google 更新了 OAuth 政策要求所有第三方应用必须使用 PKCEProof Key for Code Exchange增强安全性googlesheets4在 v1.1.0 版本中已原生支持而其他包至今未跟进。选它不是因为“它能用”而是因为“它被 Google 认证、被 R 社区背书、被生产环境验证”。2.2 为什么必须是 OAuth403 错误的真相看到OAuth error: request failed with status code 403就慌说明你还没理解 403 的本质。HTTP 403 Forbidden 不是“服务器拒绝你”而是“服务器确认你是你但你没权限”。在 Google Sheets 场景下它几乎总是指向一个具体事实你授权的 Google 账户没有被赋予该 Sheet 的查看权限。举个真实案例某电商公司的 BI 工程师小王用个人 Gmail 账户完成了googlesheets4的首次授权成功读取了测试 Sheet。但当他把脚本部署到公司服务器用服务账户Service Account运行时立刻报 403。原因很简单他个人账户有权限但服务账户没有被添加为该 Sheet 的协作者。Google 的权限模型是“账户级”的不是“应用级”的。googlesheets4只是帮你把当前登录的账户的权限“亮出来”它不能替你申请权限。另一个高频 403 场景是“域限制”Domain Restriction。很多企业用 G Suite现 Google Workspace管理员会设置“仅限本公司域名用户访问”。这时如果你用个人 Gmailgmail.com去授权即使 Sheet 设置为“组织内共享”也会因域名不匹配而 403。解决方案不是换包而是换登录账户——用你的公司邮箱yourcompany.com完成授权流程。googlesheets4会记住你最后一次成功授权的账户下次read_sheet()会自动复用该账户的 token。所以“Easy Way”的第一课不是学命令而是学权限管理。googlesheets4的价值正在于它把原本模糊的“我能不能读”问题转化成了清晰可查的“谁在读、有没有权限”问题。当你看到 403第一反应不应该是“包坏了”而应该是打开那个 Sheet 的右上角“共享”按钮检查列表里有没有你当前授权的账户。2.3read_sheet()的设计哲学少即是多read_sheet()看似简单但它背后藏着googlesheets4的核心设计思想默认行为必须安全可选参数必须明确。它不像read.csv()那样有一堆header、sep、stringsAsFactors参数它的参数精简到极致read_sheet(spreadsheet, sheet NULL, range NULL, col_names TRUE, na , skip 0, n_max Inf, .name_repair unique)其中spreadsheet是唯一必填参数它接受三种形式一个完整的 Google Sheets URL如https://docs.google.com/spreadsheets/d/1aBcDeFgHiJkLmNoPqRsTuVwXyZ/edit#gid0一个 44 位的 Sheet ID即 URL 中/d/后面、/edit前面的那串字符一个ss对象由gs4_get()返回sheet NULL的默认值意味着“读取第一个工作表”这是最安全的默认——它不会因为你 Sheet 标签名改了比如从 “Data” 改成 “Q3-Data”就突然读错表。而range NULL表示“读取整个已用区域”Used Range即 Excel 里 CtrlEnd 定位到的右下角范围这比A1:Z1000这种硬编码范围靠谱得多因为它会自动适应数据增减。这种设计牺牲了“灵活性”换来了“鲁棒性”。很多用户抱怨“为什么不能直接指定列名”答案是read_sheet()的职责是“把 Sheet 的原始结构忠实地变成 data.frame”清洗和转换是dplyr或data.table的事。强行在读取层做列名映射会导致调试困难——当你发现列名不对你得先分清是 Sheet 本身改了还是read_sheet()的映射逻辑错了。googlesheets4选择把问题边界划得清清楚楚。3. 核心细节解析与实操要点从零开始的完整授权链3.1 环境准备R、RStudio 与包安装的隐形门槛别跳过这一步。很多 403 或安装失败根源都在环境配置上。我见过最离谱的案例一位教授在 Windows 上用 R 4.0.2install.packages(googlesheets4)报错package ‘googlesheets4’ is not available for this version of R。查了一下午发现是因为 CRAN 镜像源太旧没同步新版本。解决方案不是升级 R那要重装所有包而是手动指定镜像# 在 R 控制台中执行更换为国内高速镜像 options(repos c(CRAN https://mirrors.tuna.tsinghua.edu.cn/CRAN/)) install.packages(googlesheets4)但更推荐的做法是确保你的基础环境足够“现代”R 版本必须 ≥ 4.1.0。googlesheets4依赖gargle包而gargle在 R 4.0.x 下有 TLS 握手兼容性问题尤其在 macOS 和某些 Linux 发行版上。检查方法在 R 中运行R.version.string如果显示R version 4.0.5 (2021-03-31)请升级。升级 R 不会删除已安装包update.packages(ask FALSE)即可一键更新所有包。RStudio 版本必须 ≥ 2022.02.3。旧版 RStudio 的内置浏览器WebView对现代 OAuth 流程支持不佳常出现授权页面空白、回调失败。检查方法菜单栏 Help → About RStudio。如果版本低于此请下载最新版它对 OAuth 重定向 URL 的处理更健壮。系统依赖Linux 用户需确保libcurl4-openssl-devUbuntu/Debian或libcurl-develCentOS/RHEL已安装。这是httr包googlesheets4的底层 HTTP 库编译所必需。缺失时install.packages(googlesheets4)会卡在configure: error: libcurl 7.28.0 library and headers are required with the OpenSSL TLS back-end。解决命令# Ubuntu/Debian sudo apt-get update sudo apt-get install libcurl4-openssl-dev # CentOS/RHEL sudo yum install libcurl-devel提示Windows 用户无需担心系统库RStudio 安装包已自带。但请务必关闭所有代理软件尤其是那些标榜“加速 GitHub 下载”的国产工具它们会劫持https://accounts.google.com的流量导致 OAuth 页面打不开或回调失败。3.2 第一次授权浏览器弹窗背后的完整流程这是最关键的一步也是最容易出错的环节。googlesheets4的首次授权不是“点一下安装”而是一个标准的 OAuth 2.0 授权码流程共 6 步每一步都可验证触发授权在 RStudio 中运行library(googlesheets4); gs4_auth()。这会启动一个后台进程生成一个唯一的client_id和redirect_uri通常是http://localhost:1410/并拼接出完整的授权 URL。浏览器打开RStudio 会自动调用系统默认浏览器打开类似这样的 URLhttps://accounts.google.com/o/oauth2/auth? response_typecode client_id1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com redirect_urihttp%3A%2F%2Flocalhost%3A1410%2F scopehttps%3A%2F%2Fwww.googleapis.com%2Fauth%2Fspreadsheets.readonly statexyz123...注意scope参数它明确声明了本应用只请求“只读 Sheets”权限这是 Google 安全策略强制要求的最小权限原则。登录与授权你会看到 Google 的标准登录页。输入你的 Google 账户务必是拥有目标 Sheet 权限的那个账户。登录后会出现一个授权确认页标题是“Allow access to your Google Sheets”下方列出具体权限“View your Google Sheets spreadsheets”。必须点击“Allow”。如果点“Cancel”流程终止gs4_auth()会返回NULL。重定向与代码接收授权成功后Google 会将你重定向回http://localhost:1410/?code4/...statexyz123...。这个code后面的长字符串就是一次性授权码Authorization Code。Token 交换googlesheets4的后台进程监听localhost:1410端口捕获到这个重定向后立即用code、client_id、client_secretgooglesheets4内置了官方 client credentials无需你提供、redirect_uri向 Google 的 token 端点发起 POST 请求换取access_token和refresh_token。本地存储获得的access_token有效期 1 小时和refresh_token长期有效会被加密存储在~/.R/gargle/目录下的 JSON 文件中Windows 是C:\Users\YourName\AppData\Roaming\gargle\。后续read_sheet()调用会自动读取此文件用refresh_token换新access_token全程静默。注意如果浏览器没自动弹出请手动复制授权 URL 到浏览器地址栏打开。如果重定向后页面显示This site can’t be reached或localhost refused to connect说明localhost:1410端口被占用。解决方案在 R 中运行options(gargle.port 1411)再运行gs4_auth()它会改用 1411 端口。3.3 Sheet ID 提取URL 里的“真名”与“别名”read_sheet()的spreadsheet参数最可靠的形式是44 位 Sheet ID而不是 URL。为什么因为 URL 可能包含#gid123456789工作表 ID或ouid...组织 ID等干扰参数googlesheets4解析时可能出错。提取 ID 的方法极其简单打开你的 Google Sheet。查看浏览器地址栏找到形如https://docs.google.com/spreadsheets/d/1aBcDeFgHiJkLmNoPqRsTuVwXyZAbCdEfGhIjKlMnOpQrStUvWxYz0123456789/edit#gid0的 URL。d/后面、/edit前面的那一长串就是你的 Sheet ID1aBcDeFgHiJkLmNoPqRsTuVwXyZAbCdEfGhIjKlMnOpQrStUvWxYz0123456789。复制它粘贴到 R 中read_sheet(1aBcDeFgHiJkLmNoPqRsTuVwXyZAbCdEfGhIjKlMnOpQrStUvWxYz0123456789)。实操心得我习惯把常用 Sheet 的 ID 存在一个 R 脚本里用注释标明用途例如# 销售日报主表 - 每日自动更新 SALES_SHEET_ID - 1aBcDeFgHiJkLmNoPqRsTuVwXyZAbCdEfGhIjKlMnOpQrStUvWxYz0123456789 # 用户反馈汇总表 - 每周五下午同步 FEEDBACK_SHEET_ID - 9z8y7x6w5v4u3t2s1r0q9p8o7n6m5l4k3j2i1h0g9f8e7d6c5b4a3z2y1x0这样脚本里永远用变量名ID 只维护一处避免硬编码错误。3.4read_sheet()的参数实战不只是读第一张表read_sheet()的强大在于它能把 Sheet 的复杂结构精准映射。我们用一个真实场景来演示一份名为“2024-Q3-Marketing-Campaign”的 Sheet里面有 4 个标签页Overview总览指标KPICampaign-Data详细投放数据主数据表Audience-Segments人群包定义ROI-Calculator公式计算表含大量公式非纯数据我们的目标是读取Campaign-Data表并跳过前两行的说明文字只读从第 3 行开始的数据。# 方案1用 sheet 参数指定标签页名 campaign_data - read_sheet( spreadsheet 1aBcDeFgHiJkLmNoPqRsTuVwXyZAbCdEfGhIjKlMnOpQrStUvWxYz0123456789, sheet Campaign-Data, # 明确指定标签页 skip 2 # 跳过前两行 ) # 方案2用 range 参数精确到单元格区域更高效 campaign_data - read_sheet( spreadsheet 1aBcDeFgHiJkLmNoPqRsTuVwXyZAbCdEfGhIjKlMnOpQrStUvWxYz0123456789, range Campaign-Data!A3:Z1000 # 标签页名 起始单元格 结束单元格 )range参数的优势在于性能。skip 2会让googlesheets4先读取整个Campaign-Data表可能有 10 万行再在 R 内存中丢弃前两行而range Campaign-Data!A3:Z1000是直接告诉 Google API“我只要 A3 到 Z1000 这个矩形区域”API 层就只返回这部分数据网络传输和内存占用都大幅降低。对于大表这是必选项。range的语法很灵活A1:C10当前活动标签页的 A1 到 C10 区域。Sheet1!A1:C10指定Sheet1标签页的 A1 到 C10。Sheet1!A:C整列 A、B、C。Sheet1!3:3第 3 行整行。Sheet1!A3:Z从 A3 开始到 Z 列末尾自动识别数据行数。注意range中的行列号是绝对的不随skip参数变化。skip是 R 层的后处理range是 API 层的前过滤优先用range。4. 实操过程与核心环节实现一个可复用的每日数据同步脚本4.1 完整脚本从授权到自动化下面是一个我在客户现场部署的真实脚本用于每日凌晨 3 点自动拉取销售日报 Sheet并保存为本地 CSV 备份。它包含了所有关键防护措施可直接复制使用# sales_daily_sync.R # 功能每日自动同步销售日报到本地 # 作者资深 R 博主 # 日期2024-06-15 # 1. 加载必要包 # 如果未安装自动安装 if (!require(googlesheets4, quietly TRUE)) { install.packages(googlesheets4, dependencies TRUE) } if (!require(dplyr, quietly TRUE)) { install.packages(dplyr, dependencies TRUE) } if (!require(lubridate, quietly TRUE)) { install.packages(lubridate, dependencies TRUE) } library(googlesheets4) library(dplyr) library(lubridate) # 2. 配置参数请按需修改 # Sheet ID - 替换为你自己的 44 位 ID SALES_SHEET_ID - 1aBcDeFgHiJkLmNoPqRsTuVwXyZAbCdEfGhIjKlMnOpQrStUvWxYz0123456789 # 本地保存路径 - 确保目录存在 OUTPUT_DIR - data/sales_daily dir.create(OUTPUT_DIR, showWarnings FALSE, recursive TRUE) # 日志文件路径 LOG_FILE - file.path(OUTPUT_DIR, sync_log.txt) # 3. 安全授权检查 # 检查是否已有有效 token token_status - gargle::token_info() if (is.null(token_status) || !token_status$valid) { message(【警告】OAuth token 无效或不存在将启动授权流程...) # 强制重新授权避免使用过期 token gs4_auth(email your-company-emailyourcompany.com) # 指定公司邮箱 } else { message(【信息】OAuth token 有效有效期至, format(token_status$expires_in, %Y-%m-%d %H:%M:%S)) } # 4. 执行读取与保存 tryCatch({ # 记录开始时间 start_time - Sys.time() message(paste(【开始】, format(start_time, %Y-%m-%d %H:%M:%S), - 开始读取 Sheet...)) # 读取主数据表跳过说明行只读已用区域 sales_data - read_sheet( spreadsheet SALES_SHEET_ID, sheet Daily-Sales, # 精确指定标签页名 range Daily-Sales!A2:ZZ, # 从第2行开始读取所有列直到 ZZ自动识别行数 col_names TRUE, # 第一行作为列名 na # 空单元格转为 NA ) # 数据质量检查 if (nrow(sales_data) 0) { stop(【错误】读取到空数据框请检查 Sheet 是否有数据或标签页名是否正确。) } if (ncol(sales_data) 3) { stop(【错误】数据列数过少3列可能读取了错误的区域或格式异常。) } # 添加时间戳列 sales_data - sales_data %% mutate(sync_timestamp start_time) # 生成文件名sales_20240615.csv file_name - paste0(sales_, format(Sys.Date(), %Y%m%d), .csv) file_path - file.path(OUTPUT_DIR, file_name) # 保存为 CSV不保存行名UTF-8 编码 write.csv(sales_data, file_path, row.names FALSE, fileEncoding UTF-8) # 记录成功日志 end_time - Sys.time() duration - as.numeric(difftime(end_time, start_time, units secs)) log_entry - paste(format(Sys.time(), %Y-%m-%d %H:%M:%S), SUCCESS - , nrow(sales_data), 行,, ncol(sales_data), 列, 耗时, round(duration, 2), 秒\n) cat(log_entry, file LOG_FILE, append TRUE) message(paste(【成功】, format(end_time, %Y-%m-%d %H:%M:%S), - 已保存, nrow(sales_data), 行数据到, file_path)) }, error function(e) { # 捕获所有错误记录详细信息 error_msg - paste(【失败】, format(Sys.time(), %Y-%m-%d %H:%M:%S), - 错误, conditionMessage(e), \n) cat(error_msg, file LOG_FILE, append TRUE) message(error_msg) # 可选发送邮件告警需配置 mailR 或 sendmailR # send_mail_alert(e) }) # 5. 清理可选 # 如果需要可以在此处添加清理临时文件、关闭连接等操作 # gs4_deauth() # 注销当前 token一般不需要4.2 自动化部署让脚本自己跑起来有了脚本下一步是让它每天自动执行。这里提供 Windows 和 macOS/Linux 两种方案都经过生产环境验证Windows 方案任务计划程序Task Scheduler将上述脚本保存为C:\scripts\sales_daily_sync.R。创建一个批处理文件C:\scripts\run_sync.batecho off cd /d C:\scripts C:\Program Files\R\R-4.3.2\bin\Rscript.exe sales_daily_sync.R pause打开“任务计划程序”创建基本任务触发器每天凌晨 3:00。操作启动程序程序/脚本填C:\scripts\run_sync.bat。关键设置在“常规”选项卡勾选“不管用户是否登录都要运行”并勾选“不存储密码”这样任务才能在无人值守时运行。如果勾选了“只在用户登录时运行”任务会失败因为gs4_auth()需要浏览器弹窗。macOS/Linux 方案cron确保 Rscript 在系统 PATH 中终端输入which Rscript应返回路径如/usr/local/bin/Rscript。编辑 crontabcrontab -e。添加一行每天凌晨 3 点执行0 3 * * * /usr/local/bin/Rscript /Users/yourname/scripts/sales_daily_sync.R /Users/yourname/scripts/sync.log 21关键验证cron 环境变量与你的终端不同googlesheets4的 token 默认存在~/.R/gargle/但 cron 可能找不到。解决方案是在脚本开头添加# 强制设置 HOME 环境变量确保 gargle 找到 token Sys.setenv(HOME /Users/yourname) # macOS # Sys.setenv(HOME /home/yourname) # Linux实操心得第一次部署后务必手动运行一次run_sync.bat或Rscript sales_daily_sync.R确保授权流程已完成token 已存储。cron 或任务计划程序不会帮你做这一步。另外日志文件sync_log.txt是排查问题的第一手资料我习惯每周一早上扫一眼看是否有ERROR关键字。4.3 性能优化大表读取的 3 个关键技巧当 Sheet 数据量超过 10 万行时read_sheet()默认行为会变慢。以下是我在处理百万行级营销日志表时总结的优化技巧技巧1用range精确限定而非skipn_max如前所述range Data!A2:Z比skip 1; n_max 100000快 3-5 倍因为它减少了网络传输量。实测一个 50 万行 × 20 列的表range方式耗时 8.2 秒skip方式耗时 32.7 秒。技巧2禁用col_names自动推断手动指定col_names TRUE会让googlesheets4先读取第一行再逐列判断数据类型。对于纯数字列它可能误判为字符型。更高效的方式是col_names FALSE然后用setNames()手动赋名# 读取无列名数据 raw_data - read_sheet(spreadsheet SHEET_ID, range Data!A2:Z, col_names FALSE) # 手动指定列名确保与 Sheet 第一行完全一致 colnames(raw_data) - c(date, campaign_id, impressions, clicks, cost, revenue)技巧3分块读取Chunkinggooglesheets4本身不支持分块但你可以用range模拟# 读取第1-10000行 chunk1 - read_sheet(spreadsheet SHEET_ID, range Data!A2:Z10001) # 读取第10001-20000行 chunk2 - read_sheet(spreadsheet SHEET_ID, range Data!A10002:Z20001) # 合并 full_data - bind_rows(chunk1, chunk2)这招在内存受限的服务器上特别有用避免单次读取耗尽 RAM。5. 常见问题与排查技巧实录那些踩过的坑和速查表5.1 OAuth 相关错误速查表错误信息根本原因解决方案OAuth error: request failed with status code 403当前授权账户无该 Sheet 查看权限打开 Sheet → 点击右上角“共享” → 在“已获取访问权限”列表中确认你的账户邮箱在列。若无点击“添加人员”输入你的邮箱权限选“可以查看”。Error in file(con, r) : cannot open the connection to https://...网络连接问题或 Google API 临时不可用检查网络运行gs4_rate_limit()查看剩余配额等待 1 分钟后重试。非代码问题。Error: API call failed: invalid_grantrefresh_token已失效通常因用户在 Google 账户安全设置中“管理第三方应用访问权限”并移除了该应用运行gs4_deauth()清除本地 token再运行gs4_auth()重新授权。Error: Cant get Google credentials. Please run gs4_auth()googlesheets4未初始化或gargle包未正确加载在脚本开头确保library(googlesheets4)检查gargle是否安装if (!require(gargle)) install.packages(gargle)。5.2read_sheet()行为异常排查问题read_sheet()返回空 data.frame但 Sheet 明明有数据排查步骤检查range参数是否写错比如Data!A1:B10但实际数据在C列之后。检查 Sheet 标签页名是否包含空格或特殊字符read_sheet()对空格敏感。解决方案用gs4_sheets()列出所有标签页名复制粘贴避免手输错误。运行gs4_range()函数验证范围gs4_range(spreadsheet SHEET_ID, sheet Data)会返回该标签页的已用区域如 A1:Z5000