
为什么你需要一个统一的 Python 数据处理工具链?
在 Python 数据分析的日常工作中,我们几乎离不开这三个库:pandas、Polars 和 Apache Arrow。pandas 是事实上的标准,Polars 凭借速度崛起,Arrow 则是列式内存格式的行业规范。
但当数据量上来时,问题就出现了:
# pandas 的经典困境
df = pd.read_csv('big_file.csv') # 10GB 文件直接 OOM
df = df[df['amount'] > 0] # 全量加载后才能过滤
result = df.groupby('category').sum() # 聚合操作慢得令人发指
DuckDB 的出现改变了这个局面。 它不是一个需要部署的数据库服务器,而是一个可以嵌入 Python 进程的分析引擎。配合 pandas、Polars、Arrow 的零拷贝集成,你可以在不改变工作流的前提下获得数量级的性能提升。
本文基于频道最新推送内容,系统讲解 5 个最实用的 DuckDB + Python 集成技巧。
技巧一:pandas ↔ DuckDB 零拷贝转换
很多人不知道,DuckDB 可以直接读取 pandas DataFrame,不需要先写成 CSV 再读回来。整个过程数据不出内存,没有序列化开销。
将 DataFrame 注册为临时表
import duckdb
import pandas as pd
# 假设你已经有一个 pandas DataFrame
df = pd.read_parquet('sales.parquet')
# 直接注册为表,零拷贝
con = duckdb.connect()
con.register('sales', df)
# 用 SQL 处理,结果直接转回 DataFrame
result = con.execute("""
SELECT
DATE_TRUNC('month', sale_date) AS month,
SUM(amount) AS total,
COUNT(*) AS orders
FROM sales
WHERE status = 'completed'
GROUP BY 1
ORDER BY 1
""").fetchdf()
print(result)
关键点
| 操作 | 说明 |
|---|---|
con.register('name', dataframe) | 把 DataFrame 注册为 DuckDB 临时表 |
con.execute(...).fetchdf() | 查询结果直接变回 pandas DataFrame |
| 数据传输 | 通过 Arrow 格式零拷贝,无序列化开销 |
反过来也成立——DuckDB 的查询结果可以直接变成 pandas:
# 从 DuckDB 查询 S3 上的 Parquet 文件,一步到位
df_result = con.execute("""
SELECT * FROM read_parquet('s3://bucket/data/*.parquet')
WHERE date >= '2026-01-01'
""").fetchdf()
技巧二:Polars 用户必看 — Arrow 是桥梁
Polars 基于 Apache Arrow,而 DuckDB 原生支持 Arrow。这意味着两者之间可以零拷贝传递数据。
import duckdb
import polars as pl
# Polars DataFrame → DuckDB(通过 Arrow,零拷贝)
pl_df = pl.read_parquet('sales.parquet')
con = duckdb.connect()
# 方式 A:直接传 Arrow 表
arrow_table = pl_df.to_arrow()
con.register('sales_arrow', arrow_table)
result = con.execute("SELECT category, AVG(amount) FROM sales_arrow GROUP BY 1").fetchdf()
# 方式 B:DuckDB 结果直接转回 Polars
duckdb_result = con.execute("""
SELECT category, SUM(amount) AS total_sales
FROM sales_arrow
GROUP BY 1
HAVING total_sales > 10000
ORDER BY total_sales DESC
""").fetch_arrow_table()
polars_result = pl.from_arrow(duckdb_result)
print(polars_result)
为什么这很重要?
- Polars 擅长列式计算,适合快速的逐列变换和过滤
- DuckDB 擅长复杂 SQL 查询,窗口函数、CTE、多表关联游刃有余
- 两者通过 Arrow 连接,数据不用复制,性能损失为零
你可以用 Polars 做快速探索,用 DuckDB 做重活——这才是真正的最佳组合。
技巧三:大文件处理 — 永远不要加载到内存
这是 DuckDB 最杀手级的场景。你有 10GB 的 CSV 或 Parquet 文件,pandas 根本读不进去?
import duckdb
con = duckdb.connect()
# 直接查询磁盘上的大文件,只返回需要的结果
top_categories = con.execute("""
SELECT
category,
SUM(amount) AS total_sales,
COUNT(*) AS order_count,
AVG(amount) AS avg_order_value
FROM read_csv_auto('/data/sales_2026.csv', header=true)
GROUP BY category
ORDER BY total_sales DESC
LIMIT 10
""").fetchdf()
print(top_categories)
对比 pandas 的做法:
# ❌ Pandas:先把整个文件读进内存
import pandas as pd
df = pd.read_csv('/data/sales_2026.csv') # 10GB 文件,内存爆炸
result = df.groupby('category').agg(
total_sales=('amount', 'sum'),
order_count=('amount', 'count'),
avg_order_value=('amount', 'mean')
).nlargest(10, 'total_sales')
核心优势
| 维度 | pandas | DuckDB |
|---|---|---|
| 内存占用 | 完整加载文件 | 只读需要的列和数据 |
| 10GB 文件实际读取 | 10GB+ | 可能只需 200MB |
| 谓词下推 | ❌ 不支持 | ✅ 自动优化 |
| 列式扫描 | ❌ 全列加载 | ✅ 只读所需列 |
DuckDB 使用谓词下推和列式扫描技术,对 10GB 文件可能只需要读取 200MB 的数据。内存占用从 10GB+ 降到几十 MB,这不是优化,是架构层面的代差。
技巧四:多格式文件一键读取
DuckDB 能直接读取各种格式,不需要先转换。这在日常工作中非常实用——老板发来 Excel、同事丢给你一份 JSON、历史数据还在 SQLite 里。
import duckdb
con = duckdb.connect()
# CSV
con.execute("SELECT * FROM read_csv_auto('data.csv')")
# Excel
con.execute("SELECT * FROM read_xlsx('data.xlsx')")
# JSON
con.execute("SELECT * FROM read_json_auto('data.json')")
# Parquet
con.execute("SELECT * FROM read_parquet('data.parquet')")
# 多个 Parquet 文件自动合并
con.execute("SELECT * FROM read_parquet('s3://bucket/data/*.parquet')")
# SQLite 数据库直接查询
con.execute("SELECT * FROM sqlite_scan('legacy.db', 'users')")
# MySQL/PostgreSQL 远程查询
con.execute("SELECT * FROM mysql_scan('host=localhost', 'database', 'users')")
con.execute("SELECT * FROM postgres_scan('dbname=sales host=localhost', 'orders')")
实战案例:把 Excel 报表直接变成 SQL 查询对象
import duckdb
con = duckdb.connect()
# 老板发来一个 Excel 文件,里面有 5 个工作表
sheets = con.execute("""
SELECT sheet_name
FROM read_xlsx_auto('月度报告.xlsx')
""").fetchall()
# 查询特定工作表
jan_data = con.execute("""
SELECT *
FROM read_xlsx('月度报告.xlsx', sheet='1月')
""").fetchdf()
# 合并多个月份
feb_data = con.execute("""
SELECT *
FROM read_xlsx('月度报告.xlsx', sheet='2月')
""").fetchdf()
combined = jan_data.vstack(feb_data) if hasattr(jan_data, 'vstack') else pd.concat([jan_data, feb_data])
技巧五:DuckDB 作为 pandas/Polars 的"加速引擎"
你不是必须放弃 pandas 或 Polars。DuckDB 可以作为它们的后端引擎,让慢操作变快。
用 SQL 替代慢的 groupby
import duckdb
import pandas as pd
# 传统 pandas 慢操作
# df.groupby('category').sum() 在大数据集上很慢
# 用 DuckDB 加速
con = duckdb.connect()
con.register('df', your_dataframe)
fast_result = con.execute("""
SELECT
category,
SUM(sales) AS total_sales,
AVG(sales) AS avg_sales,
COUNT(DISTINCT user_id) AS unique_users
FROM df
GROUP BY category
""").fetchdf()
Polars + DuckDB 混合使用
import duckdb
import polars as pl
con = duckdb.connect()
# 把 Polars DataFrame 转 Arrow 后注册
arrow_table = pl_df.to_arrow()
con.register('polars_df', arrow_table)
# 用 DuckDB 处理复杂 SQL,Polars 处理列变换
sql_result = con.execute("""
WITH monthly AS (
SELECT
DATE_TRUNC('month', date) AS month,
SUM(amount) AS revenue
FROM polars_df
GROUP BY 1
)
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_month_revenue,
ROUND((revenue - LAG(revenue) OVER (ORDER BY month)) * 100.0
/ NULLIF(LAG(revenue) OVER (ORDER BY month), 0), 2) AS growth_pct
FROM monthly
""").fetch_arrow_table()
final_df = pl.from_arrow(sql_result)
与传统工具对比表
| 操作 | pandas | Polars | DuckDB + Python |
|---|---|---|---|
| 读 CSV | pd.read_csv() | pl.scan_csv() | read_csv_auto() |
| 读 Parquet | pd.read_parquet() | pl.scan_parquet() | read_parquet() |
| 读 Excel | pd.read_excel() | ❌ 不支持 | read_xlsx() |
| 读 JSON | pd.read_json() | pl.scan_ndjson() | read_json_auto() |
| DataFrame→表 | 无 | 无 | con.register() |
| 查询→DataFrame | 无 | 无 | .fetchdf() |
| 查询→Polars | 无 | 无 | .fetch_arrow_table() |
| 大文件聚合 | groupby().sum() OOM | group_by().sum() | SQL GROUP BY 流式 |
| 窗口函数 | transform() | over() | OVER() |
| 远程数据源 | 需额外库 | 需额外库 | 内置支持 |
常用函数速查表
| 功能 | pandas | DuckDB SQL |
|---|---|---|
| 读取 CSV | pd.read_csv() | read_csv_auto('file.csv') |
| 读取 Parquet | pd.read_parquet() | read_parquet('file.parquet') |
| 读取 Excel | pd.read_excel() | read_xlsx('file.xlsx') |
| 读取 JSON | pd.read_json() | read_json_auto('file.json') |
| 注册 DataFrame | 不支持 | con.register('name', df) |
| 结果转 DataFrame | N/A | .fetchdf() |
| 结果转 Polars | N/A | .fetch_arrow_table() + pl.from_arrow() |
| 分组聚合 | df.groupby().agg() | SELECT ... GROUP BY |
| 窗口函数 | df.transform() | LAG()/ROW_NUMBER() OVER() |
| 日期截断 | dt.floor() | DATE_TRUNC('month', date) |
变现建议
掌握 DuckDB + Python 生态的整合能力,可以衍生出多种商业价值:
1. 数据服务产品化
将常用的数据处理流程封装成可复用的 Python 包或 API 服务。面向中小企业提供「数据清洗 + 报表生成」的一站式解决方案,按月收费 ¥299-999。
2. 技术咨询与培训
很多团队还在用 pandas 硬扛大文件,内存爆炸是常态。你可以为企业提供 DuckDB 迁移咨询,帮助他们重构数据处理管道。单项目收费 ¥5,000-20,000。
3. 自动化报表 SaaS
基于 DuckDB + Python 搭建轻量级 BI 平台,支持 CSV/Excel/Parquet 上传后自动生成 SQL 分析和可视化报表。面向电商、运营团队收费 ¥99-299/月。
4. 开源项目 + 付费增值
将本文中的工具类代码开源,建立个人品牌。通过 GitHub Sponsors、付费教程、优先技术支持等方式变现。
5. 数据工程模板市场
把 DuckDB + pandas/Polars 的集成模式打包成可复用的 ETL 模板,在模板市场或知识付费平台出售,每个模板定价 ¥49-199。
总结
DuckDB + Python 的正确打开方式:
- pandas 用户 — 用
con.register()把 DataFrame 变成表,用 SQL 替代慢的 groupby/merge - Polars 用户 — 通过 Arrow 零拷贝传递数据,Polars 做列变换,DuckDB 做聚合和窗口函数
- 大文件处理 — 永远不要
pd.read_csv()整个文件,用 DuckDB 直接查询 - 多格式支持 — CSV、Excel、JSON、Parquet、SQLite、MySQL、PostgreSQL 都能直接查
- 混合使用 — 不需要二选一,DuckDB 是 pandas/Polars 的加速引擎
记住一个原则:小数据用 pandas,大数据用 DuckDB,两者通过几行代码就能无缝切换。
📖 更多 DuckDB Python 实战技巧 → duckdblab.org