我用 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 不好而是对这类场景有以下问题1. **内存依赖** — 如果几个月的数据累积到 GB 级别Pandas 过一遍就卡了2. **可读性** — 同样的分析DuckDB 一行 SQLPandas 可能要 10 行3. **增量更新麻烦** — Pandas 做增量追加要在代码里手动管理DuckDB 直接 INSERT OR REPLACE一行搞定。## 三、核心代码68 行复制即用python#!/usr/bin/env python3DuckDB 全自动日报系统每天 8:00 cron 执行老板手机收邮件import duckdbimport smtplibimport osfrom datetime import datetime, timedeltafrom email.mime.text import MIMEText# 配置区 DB_PATH daily_report.duckdbDATA_DIR dataSMTP_HOST smtp.qq.comSMTP_PORT 465SMTP_USER yourqq.comSMTP_PASS your_smtp_auth_code # QQ邮箱 → 设置 → 账户 → 生成授权码RECIPIENTS [bosscompany.com]# def load_and_analyze():con duckdb.connect(DB_PATH)# 1. 建表如果不存在— DuckDB 的 CREATE TABLE IF NOT EXISTScon.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):continuepath os.path.join(DATA_DIR, f)# 用 DuckDB 直接读 CSV比 Pandas 快con.execute(fINSERT OR REPLACE INTO daily_salesSELECT * FROM read_csv_auto({path}))# 移走已处理文件防止重复os.rename(path, path .done)# 3. 一站式分析一条 SQL 算 6 个核心 KPIreport con.execute(SELECTstrftime(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_orderFROM daily_salesWHERE order_date CURRENT_DATE - INTERVAL 7 daysGROUP BY day, storeORDER BY day DESC, revenue DESC).fetchdf()con.close()return reportdef send_email(report_df):# 生成 HTML 表格table_html report_df.to_html(indexFalse, classesreport-table)today datetime.now().strftime(%Y-%m-%d)html fhtmlbody stylefont-family: -apple-system, sans-serif; padding: 20px;h2 每日经营日报 · {today}/h2{table_html}p stylecolor: #666; font-size: 12px; margin-top: 20px;自动生成 | DuckDB Python | 如有问题回复此邮件/p/body/html