Featured image of post DuckDB 端到端自动化报表流水线:从数据源到邮件推送的全流程实战

DuckDB 端到端自动化报表流水线:从数据源到邮件推送的全流程实战

从零搭建 DuckDB + Python 自动化报表系统,支持多 CSV 批量读取、SQL 聚合分析、Excel 格式化输出、定时任务调度与邮件推送,帮助数据分析师每月省下数十小时。

DuckDB 端到端自动化报表流水线:从数据源到邮件推送的全流程实战

工具链搭建 | 适合:每周手动整理报表的数据分析师


痛点场景

你每周一早上要花 3 小时手动整理 Excel,重复、无聊、易出错。如果用 DuckDB 搭一套自动化报表系统,这套流程可以压缩到 5 分钟,甚至全自动推送。

本文带你从零搭建一个完整的 DuckDB + Python 自动化报表流水线,涵盖数据接入、分析查询、格式输出、定时调度和邮件推送五个环节。


系统架构总览

数据源(Excel/CSV/SQL)
    ↓
DuckDB(统一查询层)
    ↓
Python 处理(数据清洗/聚合)
    ↓
输出(Excel/PDF/邮件推送)

核心优势:DuckDB 可以直接读取 Excel 和 CSV 文件,无需中间转换;SQL 表达能力强,复杂聚合一条语句搞定;配合 Python 生态可以串联整个工作流。


第一步:环境准备

pip install duckdb openpyxl pandas

验证安装:

import duckdb
print(duckdb.__version__)  # 应该输出版本号

第二步:连接数据源

假设你有一个电商销售数据文件夹 sales_data/,包含多个 CSV 文件,每个文件是一个月的销售记录。

import duckdb
import pandas as pd
from pathlib import Path

# 连接 DuckDB 持久化数据库(数据会保存到磁盘)
con = duckdb.connect('sales_automation.duckdb')

# 批量读取所有 CSV(自动合并,无需逐个读取)
con.execute("""
    CREATE TABLE all_sales AS
    SELECT * FROM read_csv_auto('sales_data/*.csv', hive_partitioning=true)
""")

# 查看数据概览
result = con.execute("DESCRIBE all_sales").fetchdf()
print(result)

关键技巧

  • read_csv_auto 会自动推断列类型,不需要手动指定 schema
  • hive_partitioning=true 支持按目录结构自动分区,文件名中的日期会被提取为分区列
  • DuckDB 的列式存储让聚合查询比 Pandas 快 10 倍以上

第三步:核心分析查询

3.1 月度销售总览

monthly_summary = con.execute("""
    SELECT 
        DATE_TRUNC('month', sale_date) AS month,
        SUM(revenue) AS total_revenue,
        COUNT(DISTINCT customer_id) AS active_customers,
        AVG(order_value) AS avg_order_value,
        SUM(CASE WHEN region = '华东' THEN revenue ELSE 0 END) AS east_china_revenue
    FROM all_sales
    GROUP BY DATE_TRUNC('month', sale_date)
    ORDER BY month DESC
""").fetchdf()

print(monthly_summary)

3.2 TOP 商品排行(含窗口函数)

top_products = con.execute("""
    SELECT 
        product_name,
        category,
        SUM(quantity) AS total_qty,
        SUM(revenue) AS total_revenue,
        RANK() OVER (ORDER BY SUM(revenue) DESC) AS revenue_rank
    FROM all_sales
    GROUP BY product_name, category
    HAVING SUM(revenue) > 1000
    ORDER BY total_revenue DESC
    LIMIT 20
""").fetchdf()

3.3 客户分层分析

customer_segment = con.execute("""
    SELECT 
        customer_id,
        COUNT(*) AS order_count,
        SUM(revenue) AS total_spend,
        MAX(sale_date) AS last_purchase,
        CASE 
            WHEN SUM(revenue) > 10000 THEN 'VIP'
            WHEN SUM(revenue) > 5000 THEN '高价值'
            WHEN SUM(revenue) > 1000 THEN '普通'
            ELSE '潜力'
        END AS segment
    FROM all_sales
    GROUP BY customer_id
    ORDER BY total_spend DESC
""").fetchdf()

第四步:生成格式化报表

4.1 Excel 格式(带样式)

import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment

def generate_excel_report(df_dict, output_path='report.xlsx'):
    with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
        for sheet_name, df in df_dict.items():
            df.to_excel(writer, sheet_name=sheet_name, index=False)
            
            # 美化表头
            ws = writer.sheets[sheet_name]
            header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
            header_font = Font(bold=True, color='FFFFFF')
            
            for cell in ws[1]:
                cell.fill = header_fill
                cell.font = header_font
                cell.alignment = Alignment(horizontal='center')
            
            # 自动调整列宽
            for column in ws.columns:
                max_length = 0
                column_letter = column[0].column_letter
                for cell in column:
                    try:
                        if len(str(cell.value)) > max_length:
                            max_length = len(str(cell.value))
                    except:
                        pass
                adjusted_width = min(max_length + 2, 50)
                ws.column_dimensions[column_letter].width = adjusted_width
    
    print(f'报表已生成:{output_path}')

# 生成多 Sheet 报表
report_data = {
    '月度汇总': monthly_summary,
    'TOP商品': top_products,
    '客户分层': customer_segment
}

generate_excel_report(report_data, 'weekly_report.xlsx')

4.2 纯文本格式(简洁版)

def generate_text_report(df_dict, output_path='report.txt'):
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write('=' * 60 + '\n')
        f.write('周报数据报告\n')
        f.write('=' * 60 + '\n\n')
        
        for sheet_name, df in df_dict.items():
            f.write(f'\n{sheet_name}\n')
            f.write(df.to_string(index=False))
            f.write('\n' + '-' * 60 + '\n')
    
    print(f'文本报告已生成:{output_path}')

第五步:定时调度(三种方案任选)

方案 A:Python schedule 库(适合本地运行)

import schedule
import time
from datetime import datetime

def run_weekly_report():
    print(f'{datetime.now()} - 开始生成周报...')
    
    # 重新加载最新数据
    con.execute("DROP TABLE IF EXISTS all_sales")
    con.execute("""
        CREATE TABLE all_sales AS
        SELECT * FROM read_csv_auto('sales_data/*.csv', hive_partitioning=true)
    """)
    
    # 执行分析查询
    monthly_summary = con.execute("""
        SELECT DATE_TRUNC('month', sale_date) AS month,
               SUM(revenue) AS total_revenue, COUNT(DISTINCT customer_id) AS active_customers
        FROM all_sales GROUP BY DATE_TRUNC('month', sale_date)
        ORDER BY month DESC
    """).fetchdf()
    
    # 输出报表
    report_data = {'月度汇总': monthly_summary}
    generate_excel_report(report_data, f'report_{datetime.now().strftime("%Y%m%d")}.xlsx')
    print('周报生成完成!')

# 每周一早上 9 点执行
schedule.every().monday.at("09:00").do(run_weekly_report)

while True:
    schedule.run_pending()
    time.sleep(60)

方案 B:系统 Cron(Linux/Mac)

crontab -e

添加以下行(每周一 9:00 执行):

0 9 * * 1 cd /path/to/project && python3 weekly_report.py

方案 C:GitHub Actions(云端免费调度)

创建 .github/workflows/report.yml

name: Weekly Report
on:
  schedule:
    - cron: '0 9 * * 1'  # 周一 9:00 UTC
  workflow_dispatch:

jobs:
  build-report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: pip install duckdb openpyxl pandas
      - name: Generate report
        run: python3 weekly_report.py
      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: weekly-report
          path: report_*.xlsx

第六步:邮件推送(可选)

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

def send_email_report(to_email, report_path, subject='周报数据报告'):
    msg = MIMEMultipart()
    msg['From'] = '[email protected]'
    msg['To'] = to_email
    msg['Subject'] = subject
    
    body = '附件为本周数据报告,请查收。'
    msg.attach(MIMEText(body, 'plain', 'utf-8'))
    
    with open(report_path, 'rb') as f:
        attachment = MIMEBase('application', 'octet-stream')
        attachment.set_payload(f.read())
        encoders.encode_base64(attachment)
        attachment.add_header('Content-Disposition', f'attachment; filename={report_path}')
        msg.attach(attachment)
    
    with smtplib.SMTP('smtp.example.com', 587) as server:
        server.starttls()
        server.login('[email protected]', 'your_password')
        server.send_message(msg)
    
    print(f'邮件已发送至 {to_email}')

与传统方案对比

维度传统 Pandas 方案DuckDB + Python 方案
多文件读取需逐个 read_csv + concatread_csv_auto('*.csv') 一行搞定
内存占用全部加载到内存列式存储,按需计算
查询性能百万行以上明显变慢百万行秒级响应
文件支持需额外库原生支持 CSV/Parquet/JSON/Excel/SQLite/PostgreSQL
部署复杂度依赖 Pandas + 多库单一 DuckDB 依赖

变现建议

这套自动化报表系统的价值远不止节省时间:

  1. SaaS 化:将报表能力封装为 API 服务,按调用次数收费,月入数千到数万元
  2. 数据产品:为中小企业提供行业数据报告订阅服务,年费制稳定收入
  3. 内部工具:在团队中推广这套系统,成为数据基础设施负责人,获得晋升机会
  4. 咨询培训:将实战经验转化为课程或咨询服务,单客收费 500-5000 元

核心思路:把一次性的报表工作变成可复用的数据产品。


学习更多 DuckDB 实战经验 → duckdblab.org

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。