Featured image of post 用 DuckDB 构建电商竞品监控服务:从数据抓取到变现的全流程

用 DuckDB 构建电商竞品监控服务:从数据抓取到变现的全流程

手把手教你用 DuckDB + AI 搭建电商竞品监控系统,自动追踪对手定价、上新和差评,形成可规模化的数据服务产品。含完整 SQL 代码和变现方案。

用 DuckDB 构建电商竞品监控服务:从数据抓取到变现的全流程

在电商行业,信息差就是利润差。卖家每天都在跟竞争对手打交道——对手降价了、上了新品、积累了差评……但没人有精力每天盯着这些数据。

这就是一个巨大的服务机会:帮电商卖家做竞品监控,每周或每月交付一份分析报告。

而 DuckDB,正是构建这种数据服务的最佳引擎。

预期收入: 新手起步月入 3,000-5,000 元,稳定后月入 10,000-15,000 元。

数据架构总览


一、为什么选 DuckDB?

传统做法是用 Python 爬虫抓数据,再用 Pandas 处理。但这种方式有几个问题:

维度Python + PandasDuckDB
内存占用全部加载到内存列式压缩,按需读取
SQL 能力需要写循环和条件逻辑直接写 SQL 聚合、JOIN
文件格式支持需要额外库原生 CSV/JSON/Parquet
脏数据处理容易整批崩溃RETURN_NULL_ON_ERROR 优雅容错
部署成本需要 Python 环境单文件二进制,零依赖

DuckDB 的核心优势在于:你能用 SQL 完成 80% 的数据清洗和聚合工作,而且速度极快。对于竞品监控这种"定期抓取 → 清洗 → 聚合 → 报告"的管道,DuckDB 是最轻量的选择。


二、系统架构设计

整个服务分为四层:

┌─────────────────────────────────────────┐
│         报告生成层 (Output)              │
│   CSV 导出 / JSON API / PDF 报表        │
├─────────────────────────────────────────┤
│         数据分析层 (DuckDB)              │
│   价格趋势 · 上新追踪 · 差评统计         │
├─────────────────────────────────────────┤
│         数据清洗层 (ETL)                 │
│   RETURN_NULL_ON_ERROR · TRY_CAST       │
│   去重 · 字段标准化                      │
├─────────────────────────────────────────┤
│         数据采集层 (Ingestion)           │
│   httpfs 远程读取 · CSV/JSON 导入        │
└─────────────────────────────────────────┘

三、第一步:采集与导入竞品数据

假设你通过某种方式(手动导出、公开 API、RPA 工具)获取了竞品数据,格式为 CSV:

store_name,product_name,price,sales_volume,date,review_count,negative_keywords
"店铺A","宠物零食大礼包",49.9,1200,2026-07-20,,好评如潮
"店铺B","宠物营养膏",89.0,800,2026-07-20,15,物流慢|包装破损

3.1 使用 DuckDB 直接读取

import duckdb

con = duckdb.connect("monitor.db")

# 直接读取远程或本地 CSV,启用错误容错
con.execute("""
    CREATE TABLE competitor_data AS
    SELECT * FROM read_csv_auto('competitors/*.csv',
        RETURN_NULL_ON_ERROR=true,
        header=true,
        sep=','
    )
""")

# 查看导入了多少行,多少行有脏数据
result = con.execute("""
    SELECT 
        COUNT(*) AS total_rows,
        COUNT(price) AS valid_price_rows,
        COUNT(sales_volume) AS valid_sales_rows,
        COUNT(*) - COUNT(price) AS null_price_count
    FROM competitor_data
""").fetchone()

print(f"总行数: {result[0]}, 有效价格: {result[1]}, 缺失价格: {result[3]}")

3.2 使用 httpfs 远程读取

如果数据源在远端服务器,DuckDB 的 httpfs 扩展可以直接读取:

CREATE TABLE remote_data AS
SELECT * FROM read_csv_auto(
    'https://example.com/data/competitors-july.csv',
    RETURN_NULL_ON_ERROR=true
);

四、第二步:数据清洗与标准化

竞品数据最大的问题是格式不统一。不同店铺的同一商品可能叫不同的名字,价格单位也不一致。

4.1 价格标准化

-- 统一价格格式,去除货币符号,转为数值
CREATE TABLE clean_prices AS
SELECT 
    store_name,
    product_name,
    TRY_CAST(regexp_replace(price::VARCHAR, '[^0-9.]', '', 'g') AS DOUBLE) AS price_cny,
    sales_volume,
    date
FROM competitor_data;

-- 查看清洗后的数据分布
SELECT 
    percentile_cont(0.25) WITHIN GROUP (ORDER BY price_cny) AS p25,
    percentile_cont(0.50) WITHIN GROUP (ORDER BY price_cny) AS median,
    percentile_cont(0.75) WITHIN GROUP (ORDER BY price_cny) AS p75,
    AVG(price_cny) AS avg_price,
    MIN(price_cny) AS min_price,
    MAX(price_cny) AS max_price
FROM clean_prices
WHERE price_cny IS NOT NULL;

4.2 商品名称模糊匹配去重

-- 使用 DuckDB 的 fuzzy 扩展进行商品名相似度计算
CREATE EXTENSION fuzzy;

-- 找出相似的商品名(用于合并同一商品的不同表述)
SELECT 
    a.product_name AS name_a,
    b.product_name AS name_b,
    similarity(a.product_name, b.product_name) AS sim_score
FROM clean_prices a
CROSS JOIN clean_prices b
ON a.rowid < b.rowid
WHERE similarity(a.product_name, b.product_name) > 0.7
LIMIT 20;

4.3 差评关键词提取与统计

-- 将逗号分隔的差评关键词拆分为独立行并统计
WITH keyword_expanded AS (
    SELECT 
        store_name,
        product_name,
        UNNEST(string_split(negative_keywords, '|')) AS keyword
    FROM competitor_data
    WHERE negative_keywords IS NOT NULL 
      AND negative_keywords != ''
)
SELECT 
    keyword,
    COUNT(*) AS mention_count,
    COUNT(DISTINCT store_name) AS affected_stores
FROM keyword_expanded
GROUP BY keyword
ORDER BY mention_count DESC
LIMIT 15;

五、第三步:核心分析——价格趋势与上新追踪

5.1 价格趋势分析

-- 按店铺统计每日平均价格变化
CREATE VIEW price_trend AS
SELECT 
    store_name,
    date,
    AVG(price_cny) AS avg_price,
    COUNT(DISTINCT product_name) AS product_count,
    MIN(price_cny) AS lowest_price,
    MAX(price_cny) AS highest_price
FROM clean_prices
GROUP BY store_name, date
ORDER BY store_name, date;

-- 计算周环比变化率
SELECT 
    store_name,
    date,
    avg_price,
    LAG(avg_price) OVER (PARTITION BY store_name ORDER BY date) AS prev_week_price,
    ROUND(
        (avg_price - LAG(avg_price) OVER (PARTITION BY store_name ORDER BY date)) 
        / NULLIF(LAG(avg_price) OVER (PARTITION BY store_name ORDER BY date), 0) 
        * 100, 2
    ) AS week_over_week_change_pct
FROM price_trend;

5.2 新品上架检测

-- 识别首次出现的产品(可能是新品)
CREATE VIEW new_products AS
SELECT 
    store_name,
    product_name,
    MIN(date) AS first_seen_date,
    COUNT(DISTINCT date) AS days_active,
    AVG(price_cny) AS avg_price
FROM clean_prices
GROUP BY store_name, product_name
HAVING MIN(date) >= DATE '2026-07-01';  -- 本月新增

-- 按店铺统计上新频率
SELECT 
    store_name,
    COUNT(*) AS new_product_count,
    ROUND(AVG(avg_price), 2) AS avg_new_product_price,
    FIRST_VALUE(product_name) OVER (
        PARTITION BY store_name ORDER BY first_seen_date
    ) AS earliest_new_product
FROM new_products
GROUP BY store_name
ORDER BY new_product_count DESC;

5.3 销量变化异常检测

-- 检测销量突增或突降的店铺
WITH daily_stats AS (
    SELECT 
        store_name,
        date,
        COALESCE(SUM(sales_volume), 0) AS daily_sales,
        AVG(SUM(sales_volume)) OVER (PARTITION BY store_name) AS moving_avg,
        STDDEV_SAMP(SUM(sales_volume)) OVER (PARTITION BY store_name) AS moving_std
    FROM competitor_data
    GROUP BY store_name, date
)
SELECT 
    store_name,
    date,
    daily_sales,
    ROUND(moving_avg, 0) AS baseline,
    ROUND((daily_sales - moving_avg) / NULLIF(moving_std, 0), 2) AS z_score
FROM daily_stats
WHERE ABS((daily_sales - moving_avg) / NULLIF(moving_std, 0)) > 2  -- Z-score > 2 为异常
ORDER BY ABS(z_score) DESC;

六、第四步:生成分析报告

分析完成后,将结果导出为客户可用的格式。

6.1 导出为结构化报告

-- 生成竞争态势概览表
COPY (
    SELECT 
        store_name AS "店铺",
        COUNT(DISTINCT product_name) AS "在售商品数",
        ROUND(AVG(price_cny), 2) AS "平均价格",
        SUM(COALESCE(sales_volume, 0)) AS "总销量",
        COUNT(DISTINCT CASE WHEN date >= DATE '2026-07-20' THEN product_name END) AS "本周上新",
        STRING_AGG(DISTINCT keyword, ', ' ORDER BY mention_count DESC) AS "主要差评点"
    FROM (
        SELECT cp.*, ke.keyword, ke.mention_count
        FROM clean_prices cp
        LEFT JOIN (
            SELECT store_name, keyword, mention_count,
                   ROW_NUMBER() OVER (PARTITION BY store_name ORDER BY mention_count DESC) AS rn
            FROM (
                SELECT store_name, 
                       UNNEST(string_split(negative_keywords, '|')) AS keyword,
                       COUNT(*) OVER (PARTITION BY store_name, UNNEST(string_split(negative_keywords, '|'))) AS mention_count
                FROM competitor_data
                WHERE negative_keywords IS NOT NULL AND negative_keywords != ''
            ) sub
        ) ke ON cp.store_name = ke.store_name AND ke.rn <= 3
    ) combined
    GROUP BY store_name
) TO 'reports/weekly_competitor_summary.csv' (HEADER);

6.2 导出 JSON 供前端展示

COPY (
    SELECT json_object_agg(store_name, json_build_object(
        'avg_price', avg_price,
        'new_products', new_product_count,
        'z_score_max', max_z_score
    ))
    FROM (
        SELECT store_name,
               ROUND(AVG(avg_price)::numeric, 2) AS avg_price,
               COUNT(*) FILTER (WHERE week_over_week_change_pct < -5) AS new_product_count,
               MAX(ABS(z_score)) AS max_z_score
        FROM (
            -- 上面定义的 price_trend 和 anomaly views
            SELECT * FROM price_trend
        ) pt
        LEFT JOIN (
            SELECT * FROM daily_stats WHERE ABS(z_score) > 2
        ) ds ON pt.store_name = ds.store_name
        GROUP BY store_name
    ) summary
) TO 'reports/summary.json';

七、与传统工具的对比

功能Python + PandasExcelMetabaseDuckDB 方案
数据采集需编写爬虫代码手动复制需配置连接器read_csv_auto() 一行
数据清洗多行代码手动筛选有限SQL TRY_CAST + RETURN_NULL_ON_ERROR
价格趋势分析需 matplotlib手动图表需创建视图SQL 窗口函数 + COPY TO
异常检测需 scipy/statsmodels条件格式不支持SQL STDDEV_SAMP + LAG
报告生成需生成 PDF/HTML手动排版需导出SQL COPY TO 直接输出
部署复杂度Docker + cron无法自动化需 Web 服务器单文件 Python 脚本 + cron
单次处理 100 万行~15 秒卡死~5 秒~0.3 秒

八、变现建议

这个服务的核心价值在于将数据转化为决策信息,让客户能做出更好的商业决策。

8.1 服务分层定价

套餐内容月费
基础版每周 1 份竞品价格报告(3-5 家店铺)¥500/月
标准版每日更新 + 异常价格预警 + 上新追踪¥1,000/月
高级版深度分析 + 选品建议 + 差评情感分析 + 月度战略报告¥2,000/月

8.2 可扩展的业务方向

  1. 垂直品类深耕:专注宠物用品、母婴、美妆等一个品类,建立行业数据库,形成竞争壁垒
  2. SaaS 化产品:将上述 DuckDB 管道封装为 Web 应用,客户自助查询
  3. 数据 API 服务:将竞品价格数据做成 API,卖给 ERP 或选品工具
  4. 咨询 + 培训:教电商团队自建竞品监控系统,收取咨询费 + 培训费
  5. 行业报告产品:基于积累的数据,发布付费行业趋势报告

8.3 技术栈成本

组件费用
DuckDB(引擎)免费开源
Python 脚本(调度)免费
VPS 服务器(运行脚本)¥50-100/月
数据存储(SQLite/Parquet)免费
报告分发(邮件/微信)免费

启动成本几乎为零,利润率极高。


九、一键部署:完整 Python 脚本

#!/usr/bin/env python3
"""
电商竞品监控服务 - 一键脚本
用法: python3 monitor.py --category 宠物用品 --days 7
"""

import duckdb
import json
import sys
from datetime import datetime, timedelta

def main(category="宠物用品", days=7):
    con = duckdb.connect(f"monitor_{category}.db")
    
    # 1. 导入原始数据
    con.execute("""
        CREATE TABLE IF NOT EXISTS raw_data AS
        SELECT * FROM read_csv_auto(
            f'data/{category}/*.csv',
            RETURN_NULL_ON_ERROR=true
        )
    """)
    
    # 2. 清洗数据
    con.execute("""
        CREATE OR REPLACE TABLE clean_data AS
        SELECT 
            store_name,
            product_name,
            TRY_CAST(regexp_replace(price::VARCHAR, '[^0-9.]', '', 'g') AS DOUBLE) AS price,
            TRY_CAST(sales_volume AS BIGINT) AS sales,
            date
        FROM raw_data
    """)
    
    # 3. 生成竞品周报
    report = con.execute("""
        SELECT 
            store_name,
            COUNT(DISTINCT product_name) AS products,
            ROUND(AVG(price), 2) AS avg_price,
            SUM(COALESCE(sales, 0)) AS total_sales,
            COUNT(*) FILTER (WHERE date >= CURRENT_DATE - INTERVAL '{days}' DAY) AS recent_activity
        FROM clean_data
        GROUP BY store_name
        ORDER BY total_sales DESC
    """.format(days=days)).fetchall()
    
    # 4. 导出报告
    with open(f'reports/{category}_report_{datetime.now():%Y%m%d}.json', 'w') as f:
        json.dump(report, f, ensure_ascii=False, indent=2)
    
    print(f"✅ 报告已生成: reports/{category}_report_{datetime.now():%Y%m%d}.json")
    con.close()

if __name__ == "__main__":
    category = sys.argv[1] if len(sys.argv) > 1 else "宠物用品"
    days = int(sys.argv[2]) if len(sys.argv) > 2 else 7
    main(category, days)

总结

用 DuckDB 构建电商竞品监控服务的核心思路是:数据采集 → 清洗 → 分析 → 报告,每一步都可以用 SQL 完成,无需复杂的代码。

DuckDB 的优势在于:

  • 轻量:单文件部署,零依赖
  • 快速:列式存储 + SIMD 优化,百万级数据毫秒级响应
  • 容错RETURN_NULL_ON_ERROR 让脏数据不再导致管道崩溃
  • 灵活:直接读取 CSV/JSON/Parquet,无需额外转换

今天就可以开始: 选一个品类,手动收集 3-5 家竞品的数据,用上面的 SQL 跑一遍分析。当你有了第一个成功案例,就可以去找你的第一批客户了。

立即行动: 打开你的电脑,创建一个 monitor.db,把上面的 SQL 跑起来。30 分钟,你就能拥有第一个竞品分析报告。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

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

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

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