我用 DuckDB + Python 搭了个全自动日报系统:68 行代码,7 个踩坑实录
我用 DuckDB + Python 搭了个全自动日报系统:68 行代码,7 个踩坑实录
总周期:3 天业余时间(每天下班 2 小时)
总成本:≈ 服务器 ¥29/月(已有)
技术栈:DuckDB + Python + cron + SMTP
代码量:68 行 Python
结果:每天 8:00 自动出日报,老板手机收邮件,30 秒看完
本文给到:完整可跑代码、7 个深坑清单、方案对比表、技术选型思路。
一、为什么是 DuckDB + Python,而不是 BI 工具?
接了个小活。朋友开连锁便利店,6 家分店,每天店长手工汇总 Excel。
Excel 里 12 个 Sheet,公式多到打开要卡 5 秒。每个月花在「做日报」上的人力成本超过 3000 块。
他问:能不能自动化?
我列了 3 个方案:
| 方案 | 月成本 | 部署周期 | 维护成本 | 我的判断 |
|---|---|---|---|---|
| Tableau / Power BI | ¥2000-5000 | 1-2 周 | 高(要专人维护) | 杀鸡用牛刀 |
| 定制开发(Spring Boot + MySQL) | ¥10000+ | 1 个月 | 高(要迭代) | 周期太长 |
| DuckDB + Python + cron | ¥500 | 3 天 | 零 | ✅ 选它 |
选 DuckDB 的核心原因:嵌入式、零运维、SQL 就够用。不需要搭数据库服务,不需要配权限系统,一个 .py 文件就是全部。
二、技术栈选型
| 组件 | 选型 | 理由 |
|---|---|---|
| 计算引擎 | DuckDB 1.1.x | 嵌入式 OLAP,单文件数据库,SQL 原生支持窗口函数 |
| 脚本语言 | Python 3.11 | cron 调度友好,生态丰富 |
| 报告格式 | HTML 邮件 | 老板手机直接看,不要下载附件 |
| 推送方式 | SMTP (QQ邮箱) | 免费、稳定、不需要额外服务 |
| 定时调度 | Linux cron | 系统自带,零依赖 |
| 数据格式 | CSV | 客户 POS 系统直接导出,不需要 ETL |
为什么不用 Pandas?
不是 Pandas 不好,而是对这类场景有以下问题:
- 内存依赖 — 如果几个月的数据累积到 GB 级别,Pandas 过一遍就卡了
- 可读性 — 同样的分析,DuckDB 一行 SQL,Pandas 可能要 10 行
- 增量更新麻烦 — Pandas 做增量追加要在代码里手动管理
DuckDB 直接 INSERT OR REPLACE,一行搞定。
三、核心代码(68 行,复制即用)
#!/usr/bin/env python3
"""
DuckDB 全自动日报系统
每天 8:00 cron 执行,老板手机收邮件
"""
import duckdb
import smtplib
import os
from datetime import datetime, timedelta
from email.mime.text import MIMEText
# ========== 配置区 ==========
DB_PATH = "daily_report.duckdb"
DATA_DIR = "data"
SMTP_HOST = "smtp.qq.com"
SMTP_PORT = 465
SMTP_USER = "your@qq.com"
SMTP_PASS = "your_smtp_auth_code" # QQ邮箱 → 设置 → 账户 → 生成授权码
RECIPIENTS = ["boss@company.com"]
# ===========================
def load_and_analyze():
con = duckdb.connect(DB_PATH)
# 1. 建表(如果不存在)— DuckDB 的 CREATE TABLE IF NOT EXISTS
con.execute("""
CREATE TABLE IF NOT EXISTS daily_sales (
order_id VARCHAR PRIMARY KEY,
order_date DATE,
store VARCHAR,
category VARCHAR,
total_amount DOUBLE,
cost DOUBLE
)
""")
# 2. 扫描 data/ 下的新 CSV 文件,增量插入
for f in os.listdir(DATA_DIR):
if not f.endswith(".csv"):
continue
path = os.path.join(DATA_DIR, f)
# 用 DuckDB 直接读 CSV,比 Pandas 快
con.execute(f"""
INSERT OR REPLACE INTO daily_sales
SELECT * FROM read_csv_auto('{path}')
""")
# 移走已处理文件,防止重复
os.rename(path, path + ".done")
# 3. 一站式分析:一条 SQL 算 6 个核心 KPI
report = con.execute("""
SELECT
strftime(order_date, '%Y-%m-%d') as day,
store,
COUNT(*) as order_count,
ROUND(SUM(total_amount), 2) as revenue,
ROUND(SUM(total_amount - cost), 2) as profit,
ROUND(AVG(total_amount), 2) as avg_order
FROM daily_sales
WHERE order_date >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY day, store
ORDER BY day DESC, revenue DESC
""").fetchdf()
con.close()
return report
def send_email(report_df):
# 生成 HTML 表格
table_html = report_df.to_html(index=False, classes="report-table")
today = datetime.now().strftime("%Y-%m-%d")
html = f"""
<html>
<body style="font-family: -apple-system, sans-serif; padding: 20px;">
<h2>📊 每日经营日报 · {today}</h2>
{table_html}
<p style="color: #666; font-size: 12px; margin-top: 20px;">
自动生成 | DuckDB + Python | 如有问题回复此邮件
</p>
</body>
</html>
"""
msg = MIMEText(html, "html", "utf-8")
msg["Subject"] = f"📊 经营日报 {today}"
msg["From"] = SMTP_USER
msg["To"] = ", ".join(RECIPIENTS)
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as s:
s.login(SMTP_USER, SMTP_PASS)
s.send_message(msg)
if __name__ == "__main__":
df = load_and_analyze()
send_email(df)
print(f"✅ 日报已发送 ({len(df)} 行数据)")
crontab 配置
# 每天 8:00 自动执行
0 8 * * * cd /home/data/report && python3 daily_report.py
搞定。
四、7 个深坑(本文最干的部分)
除了上面能跑的代码,以下是踩出来的真坑:
| # | 坑 | 现象 | 解法 |
|---|---|---|---|
| 1 | CSV 编码 | 客户 POS 导出的 CSV 是 GBK,DuckDB 读出来乱码 | read_csv_auto 加参数:encoding='gbk' |
| 2 | 日期格式不一致 | 有的分店用 2025-01-01,有的用 2025/01/01 |
DuckDB 用 strptime 统一解析,或在 SQL 里用 TRY_CAST |
| 3 | 重复数据插入 | cron 跑第二次时,同一个文件再次被导入 | INSERT OR REPLACE + 处理完的文件改后缀 .done |
| 4 | SMTP 授权码 | 第一次配 QQ 邮箱死活连不上 | 不是登录密码,是「设置→账户→生成授权码」 |
| 5 | 邮件进垃圾箱 | 老板说没收到,一看在垃圾箱 | HTML 邮件里不要有外部图片链接,纯文本+内联样式 |
| 6 | 数据量大时冷启动 | 第一个月导入历史数据时,DuckDB 默认用全部内存 | 加 SET memory_limit='2GB' 限制 |
| 7 | 老板要加字段 | 第二周说「再加个昨日同比」 | 准备时就用宽表设计,加字段不改代码结构 |
💡 第 6 条的
SET memory_limit是这个场景最容易被忽略的坑。DuckDB 默认行为是「有多少用多少」,在小服务器上(比如 2GB 内存的轻量云)会直接 OOM。
五、成本对比分析
| 方案 | 月成本 | 部署周期 | 维护成本 |
|---|---|---|---|
| 人工 Excel | ¥3000+ | 即时 | 高(耗人、易错) |
| 专业 BI 工具 | ¥2000-5000 | 1-2 周 | 高(要专人维护) |
| 定制开发系统 | ¥10000+ | 1 个月 | 高(要迭代) |
| DuckDB + Python | ¥0(已有服务器) | 3 天 | 零 |
投入产出比
这套方案的开发成本主要是时间——实际写代码 3 天。
维护成本几乎为零:cron 定时执行,除非 CSV 格式变了,否则不用管。
如果按企业外包市场价折算,一个日报自动化模块的合理开发费约 ¥3000-5000。
六、AI 帮不了你的 3 件事
写代码 DuckDB + Python 这套,AI(Claude / DeepSeek)确实能帮你搞定 80%。但以下 3 件事它替不了你:
-
谈价格 — AI 不知道老板的心理预期。一个小超市老板能接受 ¥300/月,但一个连锁店老板愿意出 ¥2000/月。这个判断只能你自己做。
-
数据清洗规则 — 每个客户的 CSV 都不一样。有的用逗号分隔,有的用制表符,有的有空行。你要跟客户沟通「你们导出时选这个格式」,AI 不知道你在跟谁说话。
-
验收标准 — 老板说「我要日报」,打开后说「这个表格怎么没有图表?」。你加图表,他又说「太多了,只要一个数字」。这个要来回磨合,AI 不在现场。
说实话,这份代码我用 DeepSeek 写了个初版花了 20 分钟。但后续的踩坑、调优、跟客户沟通,花了整整 3 天。
AI 能干 80% 的活,但剩下 20% 才是这件事真正的门槛。
七、我的工作流(CLAUDE.md 模板)
如果你也想用 AI 帮你写类似的工具,我在项目根目录放了这份 CLAUDE.md:
## 项目类型
DuckDB + Python 自动化脚本,cron 定时执行。
## 关键约束
- DuckDB 版本 ≥ 1.0,Python ≥ 3.9
- 输出格式:HTML 邮件(不是 JSON、不是 Markdown)
- 数据源:CSV(来源不可控,必须做容错)
- 运行环境:轻量云服务器,内存 ≤ 2GB
## 踩坑记录(必读)
详见底部 #Pitfalls,修改数据加载逻辑前必读。
## 命名规范
- 函数用动词开头:load_、calc_、send_
- SQL 不跨行拼接,用三重引号保持可读性
- 配置文件独立,不改核心代码
为什么这样写?
因为 AI 最容易犯的错不是写不出来,而是:
- 给你升级依赖(pip install 一个你没听说过的包)
- 改了你没让改的地方(顺手 refactor 了稳定运行的部分)
- 用了你环境不支持的特性(比如 Python 3.12 f-string)
CLAUDE.md 把「不准做什么」写清楚,比「要做什么」更重要。
总结
这套 DuckDB + Python + cron 的方案,最适合的场景:
- 中小企业老板 — 不想买 BI 工具,不想雇数据分析师
- 程序员接外包 — 给客户搭一套自动化报告,省时省力
- 个人数据监控 — 跑个自动化报告,每天邮件看看自己的业务数据
不合适的场景:
- ❌ 需要实时大屏 → 用 Grafana
- ❌ 用户自己要做交互式分析 → 用 Metabase
- ❌ 流式数据 → 用 Kafka + Flink
更多 DuckDB + Python 实战 → 搜「DuckDB 实验室」
📎 参考:
DuckDB 实战教程 → duckdblab.org
DuckDB 官方文档 → duckdb.org
有更好的方案?欢迎评论区开杠,我会一条一条回。
更多推荐



所有评论(0)