site logo

Marico's space

定时将 JSON 或 CSV 导入 Google Sheets,无需 OAuth(服务账号 + 单次 API 调用)

编程技术 2026-09-28 17:34:16 4

最近折腾定时把爬虫数据写进 Google Sheets,踩了几个坑,这篇把问题说清楚。

一次性往表格里写数据很简单,但要实现每小时自动同步、无人值守,问题就来了:

  • OAuth 需要人工介入。 在浏览器里点"用 Google 登录"很方便,但定时任务、webhook 或者 AI 代理没法帮你点击。而且刷新令牌会过期(测试模式的应用 7 天就失效)。
  • 追加不等于"写到最后一行下面"。 下一次数据多了个字段,或者字段顺序变了,你的列就悄悄错位了。
  • 重复运行会产生重复数据。 每天的价格监控、库存检查需要的是"这行存在就更新,不存在就新增"——Google Sheets API(应用程序接口)不帮你做这事。
  • 限制总是来得猝不及防。 表格最多 1000 万个单元格,写请求有每分钟速率限制,请求体也不能无限大。

这篇文章介绍服务账号这条路,以及我写的一个封装好的 Actor,直接调用就行。

第一步:创建服务账号(一次搞定,5 分钟)

服务账号是 Google Cloud 项目里的一个机器人身份。它自己签名令牌,不需要授权页面,也不用手动刷新。

  1. 打开 Google Cloud 控制台 -> 选择项目 -> 新建项目。
  2. API 和服务 -> 库 -> 搜索"Google Sheets API" -> 启用。
  3. API 和服务 -> 凭据 -> 创建凭据 -> 服务账号(不需要任何角色)。
  4. 点进刚创建的服务账号 -> 密钥 -> 添加密钥 -> 创建新密钥 -> JSON。这个文件保管好,别泄露。
  5. 打开你的 Google Sheet:共享 -> 粘贴 JSON 里的 client_email -> 编辑者权限。

这个服务账号只能访问你明确共享给它的表格。在控制台删除密钥,访问立刻终止。

第二步:发送数据行

用的 Actor 是 feedsmith/google-sheets-sync。Python 调用示例:

import json, os, requests rows = [ {"sku": "A-100", "name": "Desk lamp", "price": {"usd": 24.9}, "tags": ["home", "light"]}, {"sku": "A-101", "name": "Monitor arm", "price": {"usd": 59.0}, "tags": ["office"]},
] run = requests.post( "https://api.apify.com/v2/acts/feedsmith~google-sheets-sync/run-sync-get-dataset-items", params={"token": os.environ["APIFY_TOKEN"]}, json={ "serviceAccountKey": open("service-account.json").read(), "spreadsheet": "https://docs.google.com/spreadsheets/d/<your-sheet-id>/edit", "sheetName": "Prices", "mode": "upsert", "keyColumns": ["sku"], "rawData": rows, }, timeout=300,
)
print(json.dumps(run.json(), indent=2))
Enter fullscreen mode Exit fullscreen mode

返回一条汇总记录(真实运行,耗时 8.6 秒):

[{"mode": "upsert", "spreadsheetId": "12EqRkXF...", "sheetName": "Prices", "rowsAppended": 2, "rowsUpdated": 0, "rowsSkippedDuplicate": 0, "newColumns": ["sku", "name", "price.usd", "tags"], "totalCellsAfter": 52104, "warnings": []}]
Enter fullscreen mode Exit fullscreen mode

第二天再跑,发送 {"sku": "A-100", "price": {"usd": 19.9}} 加一个新的 SKU A-102,同样的调用。返回显示 "rowsAppended": 1, "rowsUpdated": 1,表格内容如下:

sku name price.usd tags
A-100 Desk lamp 19.9 home, light
A-101 Monitor arm 59 office
A-102 Cable tray 12.5

A-100 保留了 name 和 tags 字段,只更新了价格。

内部处理逻辑:

  • price.usd 被展平为独立列(嵌套对象变成 a.b.c 格式),tags 数组被转成 home, light 字符串。
  • upsert 模式按 sku 匹配:已存在的行原地更新,新 SKU 追加到末尾。明天再跑一遍,新价格覆盖旧价格,每个 SKU 始终只有一行。
  • 如果工作表 Prices 不存在,会自动创建。

其他模式:append(从不清空数据,新字段追加到右侧,已有的列顺序保持不变)、replace(清空整个工作表重写,旧数据会以 JSON 格式备份)和 read(把表格内容以 JSON 行数组返回,key 是表头名称)。

接在任何爬虫后面

在 Apify 控制台打开爬虫 -> 集成 -> 连接 Actor -> 选择 Google Sheets Import & Export,把 datasetId 设为 {{resource.defaultDatasetId}}。爬虫每次跑完,结果自动进表格,不需要写代码。

让 AI 代理来跑

通过 Apify MCP(模型上下文协议)服务器,代理可以直接调用这个 Actor,传入 rawData。因为不需要浏览器登录,从 Claude、Cursor 或任何 MCP 客户端调用,跟从 cron 定时任务跑,效果完全一样。

容易踩坑的细节

  • 公式注入风险。 爬取到的文本如果像 =IMPORTXML(...) 或 +1-... 这种,用 USER_ENTERED 模式写入会变成活的公式。默认是 RAW 模式;如果要用 USER_ENTERED,除非设置 allowFormulas,否则这类值会被转义处理。实测用 USER_ENTERED 时,=1+1 会作为文本存入(不是 2),+84 912 保持为字符串,2026-09-27 则被识别为真正的日期。
  • 单元格数量限制。 Actor 写入前会先统计所有工作表的单元格总数,如果超限会直接拒绝并提示"大概 N 行能放得下",不会写到一半失败。
  • 速率限制。 写入操作会分批、限速;遇到 429 或 5xx 错误会自动重试并退避。实测 20,000 行 x 6 列写入新工作表,用了 24 秒,内存峰值 50 MB。
  • 错误信息可操作。 表格没共享会报错:"请把表格以编辑者身份共享给 sheets-sync@...iam.gserviceaccount.com(Google Sheets 里的共享按钮),然后重试";API 没启用会告诉你去哪个项目启用。

费用

每次成功运行 $0.002,每处理 1000 行额外 $0.005。失败的 dry run 不收费。

算笔账:每天同步 500 行大约 $0.21/月,每小时同步一次大约 $5/月。

相关链接

  • Actor 地址:Google Sheets Import & Export
  • Google 服务账号文档:Service Account Overview

本文与 Google 无关联。利益披露:这是我写的 Actor。这篇文章由 AI(人工智能,Claude)辅助起草,所有示例输出均来自 2026-09-27 的真实运行。