Featured image of post DuckDB 自动化周报:给中小企业搭建数据产品,每月多赚 ¥5,000

DuckDB 自动化周报:给中小企业搭建数据产品,每月多赚 ¥5,000

手把手教你用 DuckDB + Python 给中小企业搭建自动化周报系统,从数据采集到邮件推送全流程。这是一个真实可落地的变现路径,单客户月费 ¥3,000-8,000。

中小企业的数据痛点:一个被严重低估的变现机会

很多数据分析师有一个误区:觉得自己的技能只能用在"大公司、大项目"上。

事实恰恰相反——中小企业才是付费意愿最强、竞争最少的金矿

为什么?因为中小企业老板每天都在看数据,但他们没有数据团队。Excel 撑不住了,找外包太贵,请全职分析师月薪 2 万起。

如果你能用 DuckDB 给他们搭一套自动化周报系统,收 ¥3,000-8,000/月,一年就是 36-96 万的收入。而且你只需要 1-2 个人。

今天把这个变现路径完整拆解给你看。

自动化周报系统架构


一、搞清楚客户要什么

假设你的客户是一家连锁餐饮品牌,有 5 家店。老板每周需要一张表:

  • 每家店的总营收、订单量、客单价
  • 环比上周的变化
  • 畅销 TOP 10 菜品
  • 差评率最高的菜品

这些数据通常存在收银系统的 CSV 导出里,或者简单的数据库里。

老板每周花 2 小时手动整理 Excel,经常出错,还总是拖到周一上午才发出来。

你的价值:帮他自动化这个过程,从"手动 2 小时"变成"打开微信看结果"。


二、用 DuckDB 搭建分析引擎

先模拟客户的数据结构。餐饮收银系统导出的 CSV 长这样:

-- 模拟订单数据(实际从 CSV 读取)
CREATE TABLE orders AS
SELECT * FROM (VALUES
    ('2026-06-23', '北京朝阳店', 1001, '宫保鸡丁', 32, 1),
    ('2026-06-23', '北京朝阳店', 1002, '麻婆豆腐', 22, 2),
    ('2026-06-23', '上海浦东店', 1003, '宫保鸡丁', 32, 3),
    ('2026-06-23', '上海浦东店', '水煮鱼', 58, 1),
    ('2026-06-24', '北京朝阳店', 1005, '水煮鱼', 58, 2),
    ('2026-06-24', '广州天河店', 1006, '宫保鸡丁', 32, 1),
    ('2026-06-24', '广州天河店', 1007, '红烧肉', 48, 2),
    ('2026-06-24', '北京朝阳店', 1008, '麻婆豆腐', 22, 3)
) AS t(order_date, store, order_id, dish, price, quantity);

-- 模拟评价数据
CREATE TABLE reviews AS
SELECT * FROM (VALUES
    ('2026-06-23', '北京朝阳店', '宫保鸡丁', 5),
    ('2026-06-23', '上海浦东店', '水煮鱼', 4),
    ('2026-06-23', '北京朝阳店', '麻婆豆腐', 3),
    ('2026-06-24', '广州天河店', '宫保鸡丁', 5),
    ('2026-06-24', '北京朝阳店', '水煮鱼', 2),
    ('2026-06-24', '广州天河店', '红烧肉', 4)
) AS t(review_date, store, dish, rating);

现在写核心分析查询——一份完整的周报 SQL

-- ========== 第一部分:门店经营概览 ==========
WITH weekly_store_metrics AS (
    SELECT 
        store,
        COUNT(DISTINCT order_id) AS order_count,
        SUM(price * quantity) AS total_revenue,
        ROUND(SUM(price * quantity) / COUNT(DISTINCT order_id), 2) AS avg_order_value,
        SUM(quantity) AS total_items_sold
    FROM orders
    WHERE order_date >= '2026-06-23' AND order_date <= '2026-06-29'
    GROUP BY store
),
prev_week_store_metrics AS (
    SELECT 
        store,
        SUM(price * quantity) AS prev_revenue
    FROM orders
    WHERE order_date >= '2026-06-16' AND order_date <= '2026-06-22'
    GROUP BY store
)

SELECT 
    s.store,
    s.order_count,
    s.total_revenue,
    s.avg_order_value,
    COALESCE(p.prev_revenue, 0) AS prev_week_revenue,
    CASE 
        WHEN COALESCE(p.prev_revenue, 0) > 0 
        THEN ROUND((s.total_revenue - p.prev_revenue) / p.prev_revenue * 100, 1)
        ELSE NULL
    END AS wow_change_pct,
    CASE 
        WHEN COALESCE(p.prev_revenue, 0) > 0 
             AND (s.total_revenue - p.prev_revenue) / p.prev_revenue > 0.05 
        THEN '📈'
        WHEN COALESCE(p.prev_revenue, 0) > 0 
             AND (s.total_revenue - p.prev_revenue) / p.prev_revenue < -0.05 
        THEN '📉'
        ELSE '➡️'
    END AS trend
FROM weekly_store_metrics s
LEFT JOIN prev_week_store_metrics p ON s.store = p.store
ORDER BY s.total_revenue DESC;

这段 SQL 的输出就是周报的核心表格——每个门店的营收、订单量、客单价,以及和上周的对比趋势。📈 表示增长超 5%,📉 表示下降超 5%。

畅销菜品 + 差评预警

老板还关心菜品层面的数据。这部分用窗口函数搞定:

-- ========== 第二部分:菜品排行 + 差评预警 ==========
WITH dish_performance AS (
    SELECT 
        o.dish,
        SUM(o.quantity) AS total_sold,
        SUM(o.price * o.quantity) AS total_revenue,
        ROUND(AVG(r.rating), 1) AS avg_rating,
        COUNT(r.rating) AS review_count
    FROM orders o
    LEFT JOIN reviews r 
        ON o.store = r.store AND o.dish = r.dish
        AND r.review_date BETWEEN '2026-06-23' AND '2026-06-29'
    WHERE o.order_date BETWEEN '2026-06-23' AND '2026-06-29'
    GROUP BY o.dish
),
ranked_dishes AS (
    SELECT *,
        RANK() OVER (ORDER BY total_revenue DESC) AS revenue_rank,
        RANK() OVER (ORDER BY avg_rating ASC) AS rating_rank,
        CASE 
            WHEN avg_rating IS NOT NULL AND avg_rating < 3.0 THEN '⚠️ 差评预警'
            WHEN avg_rating IS NOT NULL AND avg_rating >= 4.5 THEN '⭐ 好评菜品'
            ELSE '✅ 正常'
        END AS status
    FROM dish_performance
)

SELECT 
    dish AS 菜品,
    total_sold AS 销量,
    ROUND(total_revenue, 0)::INTEGER AS 营收,
    avg_rating AS 评分,
    review_count AS 评价数,
    revenue_rank AS 营收排名,
    status AS 状态
FROM ranked_dishes
ORDER BY revenue_rank;

输出结果一目了然:

宫保鸡丁:销量 6,营收 192,评分 5.0,评价 2 条,排名第 1 → ⭐ 好评菜品
水煮鱼:销量 4,营收 232,评分 3.0,评价 2 条,排名第 2 → ⚠️ 差评预警
麻婆豆腐:销量 5,营收 110,评分 3.0,评价 1 条,排名第 3 → ⚠️ 差评预警
红烧肉:销量 2,营收 96,评分 4.0,评价 1 条,排名第 4 → ✅ 正常

老板一眼就能看到:水煮鱼虽然营收第二,但评分只有 3.0,需要重点关注。


三、Python 自动化:从 SQL 到产品

SQL 写好了,接下来把它变成自动化的产品。用 Python 封装成可调度脚本:

import duckdb
import smtplib
from email.mime.text import MIMEText
from datetime import datetime, timedelta
from jinja2 import Template

class WeeklyReportGenerator:
    def __init__(self, db_path="data/orders.duckdb"):
        self.con = duckdb.connect(db_path)
    
    def generate_report(self):
        """生成完整周报"""
        today = datetime.now()
        week_start = (today - timedelta(days=today.weekday())).strftime('%Y-%m-%d')
        week_end = (today + timedelta(days=6 - today.weekday())).strftime('%Y-%m-%d')
        
        # 门店经营概览
        store_report = self.con.execute("""
            WITH weekly_store_metrics AS (
                SELECT store,
                    COUNT(DISTINCT order_id) AS order_count,
                    SUM(price * quantity) AS total_revenue,
                    ROUND(SUM(price * quantity) / COUNT(DISTINCT order_id), 2) AS avg_order_value
                FROM orders
                WHERE order_date >= ? AND order_date <= ?
                GROUP BY store
            ),
            prev_week_store_metrics AS (
                SELECT store, SUM(price * quantity) AS prev_revenue
                FROM orders
                WHERE order_date >= date_sub(?, INTERVAL 7 DAY) 
                  AND order_date <= date_sub(?, INTERVAL 7 DAY)
                GROUP BY store
            )
            SELECT s.store, s.order_count, s.total_revenue, s.avg_order_value,
                COALESCE(p.prev_revenue, 0) AS prev_week_revenue,
                CASE 
                    WHEN COALESCE(p.prev_revenue, 0) > 0 
                    THEN ROUND((s.total_revenue - p.prev_revenue) / p.prev_revenue * 100, 1)
                    ELSE NULL
                END AS wow_change_pct,
                CASE 
                    WHEN COALESCE(p.prev_revenue, 0) > 0 
                         AND (s.total_revenue - p.prev_revenue) / p.prev_revenue > 0.05 
                        THEN '📈'
                    WHEN COALESCE(p.prev_revenue, 0) > 0 
                         AND (s.total_revenue - p.prev_revenue) / p.prev_revenue < -0.05 
                        THEN '📉'
                    ELSE '➡️'
                END AS trend
            FROM weekly_store_metrics s
            LEFT JOIN prev_week_store_metrics p ON s.store = p.store
            ORDER BY s.total_revenue DESC
        """, [week_start, week_end, week_start, week_start]).fetchdf()
        
        # 菜品排行
        dish_report = self.con.execute("""
            WITH dish_performance AS (
                SELECT o.dish,
                    SUM(o.quantity) AS total_sold,
                    SUM(o.price * o.quantity) AS total_revenue,
                    ROUND(AVG(r.rating), 1) AS avg_rating,
                    COUNT(r.rating) AS review_count
                FROM orders o
                LEFT JOIN reviews r ON o.store = r.store AND o.dish = r.dish
                    AND r.review_date >= ? AND r.review_date <= ?
                WHERE o.order_date >= ? AND o.order_date <= ?
                GROUP BY o.dish
            ),
            ranked_dishes AS (
                SELECT *,
                    RANK() OVER (ORDER BY total_revenue DESC) AS revenue_rank,
                    CASE 
                        WHEN avg_rating IS NOT NULL AND avg_rating < 3.0 
                            THEN '⚠️ 差评预警'
                        WHEN avg_rating IS NOT NULL AND avg_rating >= 4.5 
                            THEN '⭐ 好评菜品'
                        ELSE '✅ 正常'
                    END AS status
                FROM dish_performance
            )
            SELECT dish, total_sold, 
                   ROUND(total_revenue, 0)::INTEGER as total_revenue,
                   avg_rating, review_count, revenue_rank, status
            FROM ranked_dishes
            ORDER BY revenue_rank
        """, [week_start, week_end, week_start, week_end]).fetchdf()
        
        return store_report, dish_report
    
    def render_html(self, store_df, dish_df):
        """渲染为 HTML 邮件"""
        html_template = """
        <h2>📊 门店经营周报 ({{ week }})</h2>
        
        <h3>🏪 门店概览</h3>
        <table>
        <tr><th>门店</th><th>订单数</th><th>营收</th><th>客单价</th><th>周环比</th><th>趋势</th></tr>
        {% for row in store_rows %}
        <tr>
            <td>{{ row.store }}</td>
            <td>{{ row.order_count }}</td>
            <td>¥{{ "%.0f"|format(row.total_revenue) }}</td>
            <td>¥{{ "%.2f"|format(row.avg_order_value) }}</td>
            <td>{{ "%.1f"|format(row.wow_change_pct) if row.wow_change_pct else '-' }}%</td>
            <td>{{ row.trend }}</td>
        </tr>
        {% endfor %}
        </table>
        
        <h3>🍽️ 菜品排行</h3>
        <table>
        <tr><th>菜品</th><th>销量</th><th>营收</th><th>评分</th><th>状态</th></tr>
        {% for row in dish_rows %}
        <tr>
            <td>{{ row.dish }}</td>
            <td>{{ row.total_sold }}</td>
            <td>¥{{ row.total_revenue }}</td>
            <td>{{ row.avg_rating }}</td>
            <td>{{ row.status }}</td>
        </tr>
        {% endfor %}
        </table>
        """
        template = Template(html_template)
        week_str = f"{store_df['order_date'].min()} ~ {store_df['order_date'].max()}"
        return template.render(
            week=week_str,
            store_rows=store_df.to_dict('records'),
            dish_rows=dish_df.to_dict('records')
        )
    
    def send_report(self, store_df, dish_df, to_email="[email protected]"):
        """发送邮件"""
        html_body = self.render_html(store_df, dish_df)
        msg = MIMEText(html_body, 'html', 'utf-8')
        msg['Subject'] = f'📊 门店经营周报 - {datetime.now().strftime("%Y-%m-%d")}'
        msg['From'] = '[email protected]'
        msg['To'] = to_email
        
        server = smtplib.SMTP('smtp.gmail.com', 587)
        server.starttls()
        server.login('[email protected]', 'your_password')
        server.send_message(msg)
        server.quit()

# 一键运行
reporter = WeeklyReportGenerator("data/orders.duckdb")
stores, dishes = reporter.generate_report()
reporter.send_report(stores, dishes)

四、部署方案与定价策略

三种部署方式

1. 本地版(¥3,000/月)

在你的 VPS 上跑 cron 任务,每周自动生成并邮件发送。适合 5 家店以下的小客户。

2. Web 看板版(¥5,000/月)

用 Streamlit 或 Gradio 搭一个简单的 Web 页面,客户登录就能看到实时数据。DuckDB 查询结果直接传给前端。

# Streamlit 看板核心代码
import streamlit as st
import duckdb

con = duckdb.connect("data/orders.duckdb")

st.title("📊 门店经营看板")

# 选择日期范围
week_start = st.date_input("起始日期", ...)
week_end = st.date_input("结束日期", ...)

# 门店概览
st.subheader("门店经营概览")
store_df = con.execute("""
    SELECT store, COUNT(*) as order_count, 
           SUM(price * quantity) as total_revenue
    FROM orders 
    WHERE order_date BETWEEN ? AND ?
    GROUP BY store
""", [str(week_start), str(week_end)]).fetchdf()

st.dataframe(store_df)

3. SaaS 多租户版(¥8,000+/月)

一个系统服务多个餐饮品牌,每个品牌独立数据库。用 DuckDB 的 ATTACH 功能管理多租户数据:

ATTACH 'client_a.duckdb' AS client_a;
ATTACH 'client_b.duckdb' AS client_b;

SELECT 'client_a' AS client, * FROM client_a.orders
UNION ALL
SELECT 'client_b' AS client, * FROM client_b.orders;

获客渠道

  • 在闲鱼/淘宝搜"数据分析外包",主动联系那些还在用 Excel 的商家
  • 去本地餐饮协会、商会做免费分享,现场演示 DuckDB 分析效果
  • 在小红书发"帮餐饮老板省 10 小时/周"的案例,吸引精准客户

五、与传统方案对比

为什么用 DuckDB 而不是其他方案?

维度DuckDBExcelPython + PandasBI 工具(Tableau/Power BI)
上手难度⭐ 极低⭐ 低⭐⭐ 中等⭐⭐⭐ 高
处理百万级数据⚡ 秒级❌ 卡死✅ 可以✅ 可以
部署成本¥200/月 VPS免费¥200/月 VPS¥500-2000/月
自动化能力✅ 完美❌ 需 VBA✅ 完美⚠️ 有限
客户感知价值⭐⭐⭐ 专业报表⭐ 普通表格⚠️ 需前端包装⭐⭐ 看板
维护成本极低

DuckDB 的核心优势在于:它让你用极低的成本,交付专业的数据产品。


六、为什么这个生意能成?

  1. 技术门槛低但交付价值高:DuckDB 的 SQL 能力足以覆盖 90% 的分析需求,但你交付的是一个"每周自动收到的专业报表"——客户感知到的价值远大于你的成本。

  2. 续费率高:一旦老板习惯了每周自动收到报表,他就不会取消。这是典型的"用了就回不去"的产品。

  3. 可扩展:一个客户跑通后,复制到 10 个客户的边际成本几乎为零。DuckDB 在内存中运行,一台 ¥200/月的 VPS 就能服务几十个客户。

  4. 差异化竞争:大多数数据分析师在和大厂抢岗位,而中小企业的自动化报表市场几乎是空白。


七、变现路径总结

找到 1 个有数据的中小企业 
  → 用 DuckDB 搭建分析查询 
  → Python 封装成自动化脚本 
  → 每周定时生成并推送 
  → 按月收费 ¥3,000-8,000

不需要复杂的架构,不需要大数据集群,DuckDB 单机就够了。

下一个客户可能就是你今晚就能联系上的那个人。


延伸学习

本文的代码模板(含 Streamlit 看板、多行业适配、cron 定时任务配置)已整理成完整项目。如果你想深入学习 DuckDB 在商业场景中的应用方法,包括更多行业案例和交付 SOP,推荐访问 duckdblab.org 查看完整教程系列。

📖 完整可运行代码仓库、Streamlit 看板模板和 3 个不同行业的周报案例已发布在 duckdblab.org,包含从获客到交付的全流程 SOP。深入学习 DuckDB 变现方法论 → duckdblab.org

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

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

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

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