我用 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 一行 SQL,Pandas 可能要 10 行
  3. 增量更新麻烦 — 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 件事它替不了你:

  1. 谈价格 — AI 不知道老板的心理预期。一个小超市老板能接受 ¥300/月,但一个连锁店老板愿意出 ¥2000/月。这个判断只能你自己做。

  2. 数据清洗规则 — 每个客户的 CSV 都不一样。有的用逗号分隔,有的用制表符,有的有空行。你要跟客户沟通「你们导出时选这个格式」,AI 不知道你在跟谁说话。

  3. 验收标准 — 老板说「我要日报」,打开后说「这个表格怎么没有图表?」。你加图表,他又说「太多了,只要一个数字」。这个要来回磨合,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

有更好的方案?欢迎评论区开杠,我会一条一条回。

更多推荐