Featured image of post DuckDB 自动化报告PDF生成:从CSV到可售卖数据报告的完整流水线

DuckDB 自动化报告PDF生成:从CSV到可售卖数据报告的完整流水线

用 DuckDB + Python + ReportLab 搭建一键生成专业PDF数据报告系统,覆盖CSV读取、多维聚合、图表生成、中文PDF排版全流程,附变现路径。

DuckDB 自动化报告流水线

工具链搭建 | 适合:每周手动整理报表的数据分析师、接私单的自由职业者


痛点:一份报告从2小时到5分钟

很多数据分析师接私单时,最耗时的环节不是分析,而是把分析结果排版成漂亮的PDF交付给客户。每次手动做报表,成本高、易出错、客户满意度还低。

用 DuckDB + Python + ReportLab,可以把这个流程从2小时压缩到5分钟,并且支持批量生成不同客户的报告,边际成本趋近于零。


第一步:用 DuckDB 完成核心分析

import duckdb
import pandas as pd

con = duckdb.connect()

# read_csv_auto 自动推断列类型,无需提前定义 schema
analysis_sql = """
SELECT
    DATE_TRUNC('month', order_date) AS month,
    product_category,
    COUNT(*) AS order_count,
    SUM(amount) AS total_revenue,
    AVG(amount) AS avg_order_value,
    COUNT(DISTINCT customer_id) AS unique_customers
FROM read_csv_auto('orders.csv')
GROUP BY month, product_category
ORDER BY month DESC, total_revenue DESC
"""

result = con.sql(analysis_sql).df()
print(result.head(10))

DuckDB 相比传统方式的优势:

方式读取CSV类型推断聚合性能
Pandas + 手写需指定列名手动 astype需循环
SQLite需先导入需建表定义一般
DuckDBread_csv_auto 一行搞定全自动列式存储,毫秒级

read_csv_auto 是 DuckDB 的杀手级功能——直接读 CSV 文件,自动推断分隔符、列类型、日期格式,省掉所有预处理步骤。对于多文件场景,还支持 glob 通配符批量合并:read_csv_auto('sales_data/*.csv')


第二步:生成报告图表

import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg')  # 无头模式,适合服务器部署

fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle('月度销售数据分析报告', fontsize=16, fontweight='bold')

# 1. 各品类月度收入趋势
pivot_data = result.pivot(index='month', columns='product_category', values='total_revenue')
pivot_data.plot(kind='line', ax=axes[0, 0], marker='o')
axes[0, 0].set_title('品类收入趋势')
axes[0, 0].tick_params(axis='x', rotation=45)

# 2. 本月品类收入占比
latest_month = result['month'].max()
monthly_data = result[result['month'] == latest_month]
monthly_data.plot.pie(y='total_revenue', ax=axes[0, 1],
                       labels=monthly_data['product_category'], autopct='%1.1f%%')
axes[0, 1].set_title(f'{latest_month} 品类收入占比')

# 3. 客单价分布
axes[1, 0].hist(result['avg_order_value'], bins=20, color='steelblue', alpha=0.7)
axes[1, 0].set_title('平均客单价分布')
axes[1, 0].set_xlabel('金额 (元)')

# 4. 客户数增长
pivot_customers = result.pivot(index='month', columns='product_category',
                                values='unique_customers')
pivot_customers.plot.bar(stacked=True, ax=axes[1, 1])
axes[1, 1].set_title('各品类月度客户数')
axes[1, 1].tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.savefig('report_charts.png', dpi=150, bbox_inches='tight')
plt.close()
print('图表已生成')

第三步:打包成专业 PDF 报告

from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

# 注册中文字体
try:
    pdfmetrics.registerFont(TTFont('Chinese', '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc'))
    font_name = 'Chinese'
except:
    font_name = 'Helvetica'

doc = SimpleDocTemplate('sales_report.pdf', pagesize=A4,
                        rightMargin=2*cm, leftMargin=2*cm,
                        topMargin=2*cm, bottomMargin=2*cm)

styles = getSampleStyleSheet()
title_style = ParagraphStyle('Title', parent=styles['Title'],
                              fontName=font_name, fontSize=18, spaceAfter=20)
heading_style = ParagraphStyle('Heading', parent=styles['Heading1'],
                                fontName=font_name, fontSize=14, spaceAfter=10)
body_style = ParagraphStyle('Body', parent=styles['Normal'],
                             fontName=font_name, fontSize=10, leading=16)

story = []

# 标题
story.append(Paragraph('月度销售数据分析报告', title_style))
story.append(Paragraph(f'生成日期:{pd.Timestamp.now().strftime("%Y年%m月%d日")}', body_style))
story.append(Spacer(1, 0.5*cm))

# 执行摘要
total_revenue = result['total_revenue'].sum()
total_orders = result['order_count'].sum()
avg_value = result['avg_order_value'].mean()
story.append(Paragraph('一、执行摘要', heading_style))
summary_text = f"""本次分析基于 {len(result)} 条记录,涵盖 {result['product_category'].nunique()} 个产品品类。
关键指标:总营收 {total_revenue:,.0f} 元,订单数 {total_orders:,} 笔,平均客单价 {avg_value:.2f} 元。"""
story.append(Paragraph(summary_text, body_style))
story.append(Spacer(1, 0.5*cm))

# 图表
story.append(Paragraph('二、数据可视化分析', heading_style))
story.append(Image('report_charts.png', width=450, height=320))
story.append(Spacer(1, 0.5*cm))

# Top 10 数据表
story.append(Paragraph('三、品类明细 Top 10', heading_style))
top10 = result.nlargest(10, 'total_revenue')
table_data = [['月份', '品类', '订单数', '总收入', '平均客单价']]
for _, row in top10.iterrows():
    table_data.append([str(row['month'])[:10], row['product_category'],
                       str(int(row['order_count'])),
                       f"{row['total_revenue']:,.0f}",
                       f"{row['avg_order_value']:.2f}"])

table = Table(table_data, colWidths=[2.5*cm, 3*cm, 2*cm, 3*cm, 3*cm])
table.setStyle(TableStyle([
    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2C3E50')),
    ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
    ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
    ('FONTNAME', (0, 0), (-1, 0), font_name),
    ('FONTSIZE', (0, 0), (-1, 0), 10),
    ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
    ('GRID', (0, 0), (-1, -1), 1, colors.grey),
    ('BACKGROUND', (0, 1), (-1, -1), colors.HexColor('#ECF0F1')),
]))
story.append(table)

doc.build(story)
print('PDF报告已生成:sales_report.pdf')

进阶:打造可复用的配置驱动 Pipeline

上面的代码可以封装成配置驱动的 Pipeline,只需修改 YAML 配置文件即可切换不同客户:

import yaml
import json
from datetime import datetime

def run_report(client_config_path='clients/default.yaml'):
    with open(client_config_path) as f:
        config = yaml.safe_load(f)

    # DuckDB 执行分析
    con = duckdb.connect()
    sql = config['sql_template'].format(
        date_range=config.get('date_range', 'last_30_days')
    )
    result = con.sql(sql).df()

    # 渲染 PDF
    output_name = f"reports/{config['name']}_{datetime.now().strftime('%Y%m%d')}.pdf"
    render_pdf(result, config, output_path=output_name)
    print(f'✅ {config["name"]} 报告已生成')

# clients/ecommerce_a.yaml:
# name: 电商客户A
# sql_template: "SELECT * FROM read_csv_auto('orders_a.csv') WHERE ..."
# date_range: last_30_days

# 改配置不改代码,一天批量生成10+份报告

变现路径:一份报告能赚多少钱

模式单次收费月均可复制年化收入参考
单次交付500-2,000元20-40份12-96万
订阅制年费 5,000-20,000元/客户5-10客户5-20万/年
SaaS平台自助报告,按次收费无限放大边际成本趋近0

核心逻辑:DuckDB 负责数据处理(快、准、省资源),Python + ReportLab 负责渲染输出,YAML 配置实现多客户复用。三者结合后,单次报告的边际成本几乎为零。


与传统工具的对比

工具读取CSVSQL聚合PDF生成中文字体配置驱动
Excel + VBA手动透视表打印导出复杂
Pandas + matplotlib需列定义需写代码需额外库需处理中等
Power BI图形界面DAX导出PDF内置模板驱动
DuckDB + Python自动推断SQL一行ReportLabTTFont注册YAML配置

结尾:下一步行动建议

  1. 立即上手:准备一份 CSV 数据,复制上面的三段代码,跑通全流程
  2. 迭代优化:根据你的报告模板调整 ReportLab 样式,加入公司 Logo 和页眉页脚
  3. 规模化:用 YAML 配置驱动多客户,一次开发,批量输出
  4. 变现:先从一个真实客户开始,用自动化报告替代手工报表,验证付费意愿

📖 详细图文教程见 duckdblab.org

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

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

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

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