数据管理中级阅读约 5 分钟

如何将 DuckDB 连接到 Google Sheets

使用 Python 和 Sheets API 通过 DuckDB SQL 查询 Google Sheets 数据。在不足 50 行代码中对实时电子表格数据进行联接、聚合和 GROUP BY 操作。

Google Sheets 是你的数据存放地。DuckDB 是进行严肃 SQL 分析的地方。本指南将它们连接起来——这样你可以对实时电子表格数据运行 GROUP BY、窗口函数和多表联接,然后将结果写回新工作表。到最后,你将拥有一个可运行的 Python 脚本,它从 Sheets 读取数据到内存中的 DuckDB 实例,对其进行查询,并将结果推回。

开始前的准备

  • 在本地安装 Python 3.9 或更高版本
  • 已启用 Sheets API 的 Google Cloud 项目
  • 已下载 JSON 凭据文件的 Google 服务账户
  • 对 SQL 有基本了解,能从终端运行 Python 脚本
  • 包含表格数据的 Google Sheet(第 1 行为列标题)

分步指南

1

安装 Python 依赖项

安装此流程依赖的 3 个软件包:duckdb 用于 SQL 引擎,gspread 用于 Sheets API 包装器,google-auth 用于服务账户认证。```bash pip install duckdb gspread google-auth

  • duckdb 0.10+ 推荐使用——早期版本缺少一些 CSV 解析改进
  • gspread 6.x 改变了其认证 API;使用 pip show gspread 确认你使用的是 6.x 版本
  • google-auth 自动处理 OAuth 令牌刷新——你无需手动管理令牌

Pro Tip

使用虚拟环境(python -m venv venv && source venv/bin/activate)将这些依赖与系统 Python 隔离。
2

创建 Google 服务账户

服务账户让你的脚本以非人类身份进行身份验证,可以在没有用户交互的情况下读写电子表格——对于在计划上运行或在服务器上运行的脚本至关重要。

  • 转到 Google Cloud Console → IAM & Admin → Service Accounts
  • 点击 Create Service Account,将其命名为类似 duckdb-sheets-connector 的名称
  • 跳过可选的 IAM 角色分配——权限在电子表格级别控制
  • 在服务账户的 Keys 选项卡下,点击 Add Key → Create new key → JSON
  • 将下载的 .json 文件保存为项目目录中的 credentials.json

Pro Tip

永远不要将 credentials.json 提交到 git 仓库。立即将其添加到 .gitignore。
3

与服务账户共享电子表格

服务账户有一个电子邮件地址(在 JSON 文件中的 client_email 下可见,格式为:[email protected])。Google Sheets 将其视为任何其他用户。

  • 打开要查询的电子表格
  • 点击 Share,粘贴服务账户电子邮件地址
  • 授予 Editor 访问权限(如果要写回结果,则需要;Viewer 适用于只读)
  • 从 URL 复制电子表格 ID——它是 /d/ 和 /edit 之间的长字符串
4

将 Sheets 数据加载到 DuckDB

Google Sheets 返回数据为字符串的二维数组。下面的代码获取命名工作表,使用第一行作为标题将其转换为字典列表,然后将其注册为 DuckDB 关系。```python import duckdb import gspread from google.oauth2.service_account import Credentials

SCOPES = ["https://www.googleapis.com/auth/spreadsheets"] SPREADSHEET_ID = "your-spreadsheet-id-here" WORKSHEET_NAME = "Sheet1"

Authenticate and open the spreadsheet

creds = Credentials.from_service_account_file("credentials.json", scopes=SCOPES) gc = gspread.authorize(creds) sheet = gc.open_by_key(SPREADSHEET_ID).worksheet(WORKSHEET_NAME)

Fetch all records as list of dicts

records = sheet.get_all_records() # Uses row 1 as column headers automatically

Load into DuckDB

con = duckdb.connect() con.register("sheet_data", duckdb.from_arrow( import("pyarrow").Table.from_pylist(records) ))

  • get_all_records() 将每一行返回为字典——1,000 行表通过 API 需要大约 300ms
  • DuckDB 的 register() 使数据可查询而无需将其复制到磁盘
  • 列类型从数据推断;看起来像数字的字符串自动变为 DOUBLE

Pro Tip

对于超过 5,000 行的表,考虑使用 sheet.get('A1:Z5000') 和 sheet.get('A5001:Z10000') 进行批处理,以保持在 Sheets API 的每个请求大小限制内。
5

对 Sheets 数据运行 SQL 查询

注册数据后,你编写标准 SQL。DuckDB 支持窗口函数、CTEs、LATERAL 联接和大多数 PostgreSQL 兼容语法——这些都不存在于 Sheets 公式中。```python

Aggregation example: total revenue by region

result = con.execute(""" SELECT region, COUNT(*) AS deal_count, SUM(CAST(revenue AS DOUBLE)) AS total_revenue, AVG(CAST(revenue AS DOUBLE)) AS avg_deal_size FROM sheet_data WHERE status = 'Closed Won' GROUP BY region ORDER BY total_revenue DESC """).fetchdf() print(result)

  • DuckDB 在笔记本电脑上在 2 秒内处理 1,000 万行——比同一数据上的透视表快得多
  • fetchdf() 返回 pandas DataFrame;使用 fetchall() 获取普通元组列表
  • 来自 Sheets 的字符串列通常需要显式 CAST——Sheets 不强制执行类型,因此 revenue 可能以 "$12,000" 而不是 12000 的形式出现

Pro Tip

在转换前使用 REPLACE(REPLACE(revenue, '$', ''), ',', '') 在 SQL 中清理脏的 Sheets 数据。比在表中清理更容易。
6

将查询结果写回 Google Sheets

运行分析后,将结果 DataFrame 推回新的或现有工作表。下面的模式清除目标表并完全重写它——对于计划的工作可靠,尽管不是最适合人们正在积极编辑的表。```python

Create or clear the output worksheet

try: output_sheet = gc.open_by_key(SPREADSHEET_ID).worksheet("DuckDB Output") output_sheet.clear() except gspread.exceptions.WorksheetNotFound: output_sheet = gc.open_by_key(SPREADSHEET_ID).add_worksheet( title="DuckDB Output", rows=1000, cols=20 )

Write headers + data

headers = result.columns.tolist() rows = result.fillna("").values.tolist() output_sheet.update([headers] + rows)

  • update() 接受 2D 列表并从 A1 开始写入
  • fillna("") 将 NaN 转换为空字符串——Sheets API 拒绝 Python 的 NaN 类型
  • Sheets API 配额为每个项目每分钟 300 个写入请求;对于大型结果集,使用 batch_update() 代替
7

自动化流程

一次性脚本很有用。计划的脚本是数据流程。将逻辑包装在函数中,并按 cron 计划运行,以保持输出表的新鲜。```python import schedule import time

def refresh_analysis(): records = sheet.get_all_records() con.register("sheet_data", duckdb.from_arrow( import("pyarrow").Table.from_pylist(records) )) result = con.execute(""" SELECT region, SUM(revenue) as total FROM sheet_data GROUP BY region """).fetchdf() output_sheet.clear() output_sheet.update([result.columns.tolist()] + result.fillna("").values.tolist()) print(f"Refreshed at {import('datetime').datetime.now()}")

schedule.every(30).minutes.do(refresh_analysis) while True: schedule.run_pending() time.sleep(60)

  • 在小型虚拟机上运行此脚本($5/月实例可轻松处理)以保持 Sheets 仪表板活跃
  • 根据 Google 的 Sheets API 文档,读请求限制为每个项目每分钟 300 个——30 分钟的刷新间隔使你保持在配额范围内
  • 对于生产用途,在 API 调用周围添加错误处理,并将失败记录到单独的监控表

Pro Tip

将电子表格 ID 和工作表名称存储在环境变量(os.environ.get("SPREADSHEET_ID"))中,而不是硬编码。使脚本可在项目间重用。

总结

你已将 DuckDB 连接到 Google Sheets:通过 Sheets API 获取实时数据,将其加载到内存中的 DuckDB 实例,运行 Sheets 公式无法触及的 SQL,以及将结果写回。相同的模式可扩展到多个工作表、跨表联接以及保持报告选项卡最新的计划流程,无需任何手动导出。

这暴露的摩擦是真实的:每次源表更改架构(某人重命名列、添加选项卡)时,你的 SQL 会无声地中断。这是临时分析和适当数据流程之间的差距。如果你在复杂模型中反复遇到这个问题,值得查看 ModelMonkey——它将 DuckDB 查询层保留在 Sheets 内,你的协作者可以看到正在运行的内容。

常见问题

我需要将 Google Sheet 发布到网络以使用 DuckDB 查询它吗?

不需要。服务账户方法通过 OAuth 进行身份验证——你的表保持私密,仅与服务账户电子邮件共享。只有在使用 DuckDB 的 read_csv 函数直接获取公开导出 URL 时,才需要发布到网络,这完全跳过身份验证但公开暴露你的数据。

DuckDB 可以从 Google Sheets 处理多少行?

DuckDB 本身在内存中可处理数百万行(受 RAM 限制,而非 DuckDB)。实际瓶颈是 Sheets API:50,000 行表上的 get_all_records() 需要 3-8 秒并触发负载限制。Google 的文档指出 Sheets 限制每个电子表格 1,000 万个单元格。对于超过 ~20,000 行的表,批处理 get() 调用或先导出为 CSV,然后直接使用 duckdb.read_csv()。

我可以在单个 DuckDB 查询中联接来自两个不同 Google Sheets 的数据吗?

可以。将每个表获取到单独的字典列表中,将每个注册为不同的 DuckDB 关系(con.register("orders", ...)、con.register("customers", ...)),然后使用标准 SQL 联接它们。DuckDB 在内存中处理联接——无需数据库服务器。

如果我的表中的列有混合类型(某些单元格为数字,某些为文本),会发生什么?

DuckDB 从数据示例推断列类型。如果列有混合类型,它默认为 VARCHAR。你需要在 SQL 中显式转换:CAST(TRY_CAST(revenue AS DOUBLE) AS DOUBLE)。TRY_CAST 变体在不可转换的值上返回 NULL 而不是报错,这对于脏的 Sheets 数据更安全。

有没有办法不用 Python 就用 DuckDB 查询 Google Sheets?

DuckDB 的 read_csv 函数可以直接获取任何公开 CSV URL:SELECT * FROM read_csv('https://docs.google.com/spreadsheets/d/{ID}/export?format=csv')。这需要表被发布或公开共享。对于私有表,从 2026 年 3 月起,Python 与服务账户是唯一支持的方法。