恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
化妆品销售大数据系统实战:从Excel透视表到Hadoop数仓
首页
资讯中心
/
化妆品销售大数据系统实战:从Excel透视表到Hadoop数仓
化妆品销售大数据系统实战:从Excel透视表到Hadoop数仓
发布时间:2026/10/3 23:23:05
我在帮一家美妆零售品牌梳理销售数据时他们最常用的工具是一张Excel透视表。每天从电商平台、线下专柜、会员小程序手动导出数据再合并成日报耗时要两三个小时。到了大促节点订单量一上来透视表直接卡死更别提算不同品牌价格带的连带率会员复购周期这类跨表指标Excel基本做不动。这也是基于大数据的化妆品销售系统2025这个题目对我最有吸引力的地方它不是单纯堆Hadoop组件而是用一套标准的大数据链路把零散的化妆品销售数据变成能支撑决策的看板。这套系统适合谁准备做大数据方向毕业设计或实战项目的同学、想从BI报表工程师往数据仓库方向转的从业者以及品牌方想做内部数据中台但缺少整体思路的人。下面我把这套系统的完整实现思路、技术选型理由、核心代码和踩坑记录拆开讲。1. 化妆品销售数据为什么要建一套大数据系统1.1 单一Excel/ERP时代做不了的事先看化妆品销售数据的典型场景一个品牌同时有线上天猫/京东/抖音旗舰店线下有几十家专柜还可能有屈臣氏这类经销渠道。每个渠道的商品口径不完全一样线上订单里优惠券分摊可能精确到单品线下POS里折扣字段可能为空会员小程序里记录的又是另一套客户信息。以前用ERP加Excel勉强能做日销汇总。但业务方一旦问这几个问题Excel就非常吃力今天GMV涨了5%到底是哪个渠道、哪个品类、哪个价格带贡献的一支新品口红上市四周从曝光到加购到支付到复购漏斗数据怎么串买精华的用户同时购买面霜的比例是多少柜姐该不该做连带推荐话术上个月投放了代言人短视频新客成本到底降了没有会员复购周期是45天还是90天该在什么时间节点发优惠券这些问题的共同点需要把订单明细、商品主数据、会员表、广告投放表放在一起做关联计算还得按任意维度下钻。Excel透视表只能满足单表聚合跨表join多了就乱套。所以当业务指标复杂度上来建一套大数据系统是必然选择。1.2 销售系统要回答的核心业务问题做系统前先定指标指标没定义清楚后面建模全是白搭。我在化妆品销售系统里最终圈定了六组核心指标指标类型具体指标计算口径销售规模GMV、订单量、销量按支付成功时间统计剔除退款/取消货品结构品类销售占比、Top SKU、价格带分布价格带按商品吊牌价分档连带能力连带率、件单价购买件数/订单数GMV/订单数复购质量复购率、复购周期指定周期内购买次数≥2的人数/购买总人数渠道健康渠道贡献占比、同比/环比按线上/线下/经销商拆分活动效果活动期GMV贡献、新客占比按营销活动ID标记订单这套指标并不算多但每一条落到数据层面都需要明细数据支撑。比如价格带分布如果ODS层没有统一的商品主数据价格带算出来就各渠道不一致。这也是为什么做系统之前一定要先梳理清楚业务口径。1.3 2025年这个系统值不值得自研你可能会问现在商用BI工具那么多直接接一个不就行了我的判断是分情况。如果只是做几张固定报表确实不需要自研但如果要做销售大屏、多维度自助分析、以及支持后续算法模型比如销量预测、补货建议那底层必须有一份统一清洗和建模过的数据。商用BI解决的是可视化这一层解决不了数据口径不统一明细数据质量差这些源头问题。从学习和毕设角度看这个项目更是值得完整走一遍。因为化妆品销售场景足够贴近业务数据量可以模拟到百万级技术栈覆盖采集、清洗、建模、可视化、部署谈项目时每条链路都能讲出细节。比单纯跑通一个WordCount有说服力得多。2. 系统总体架构与2025年技术栈选型依据2.1 一条数据从订单产生到看板的完整路径先说整体链路。订单在线上商城/门店POS产生后会落到MySQL业务库同时应用服务器输出订单日志。数据采集层负责把日志和业务库数据同步到大数据平台随后用Spark做清洗转换写入Hive数仓分层表数仓完成指标汇总后把ADS层结果导出到MySQL供可视化服务查询最后Flask提供JSON接口ECharts把数据渲染成大屏。这里面没有做秒级实时而是以T1离线为主、小时级调度为辅。原因很实在化妆品销售分析不需要股票那种毫秒级响应业务方早上看昨日数据就足够了。把离线链路做扎实比盲目上实时更稳。2.2 存储选型HDFS、Hive、MySQL各自分工我在这个系统里用了三种存储职责分得很清楚HDFS存原始订单日志、渠道导出文件。它是整个数仓的地基胜在扩展便宜存几百GB很轻松。Hive做离线数仓的核心存储和计算引擎。ODS、DWD、DWS、ADS分层都建在Hive里用ORC格式加Snappy压缩查询性能和存储成本都比较理想。MySQL存ADS层最终结果和维表。数据量不大几万到几十万行前端Flask查询毫秒级返回没必要让大屏请求直接打Hive。还有人问为什么不用ClickHouse当统一数仓。我的看法是ClickHouse的MPP架构确实查询快但它的强项在于宽表查询事务和update能力弱不适合作为ODS到DWD的清洗层Hive加Spark的批处理能力覆盖全链路后期如果想加速查询可以单独把DWS层导出到ClickHouse。不要一上来就上重武器。2.3 计算选型为什么批处理仍然占主导计算引擎选了Spark而不是纯MapReduce原因不用多说MapReduce在复杂ETL和多次join场景下慢得让人难受。Spark的DataFrame API写清洗逻辑非常顺手内存计算比MR快一到两个量级。实时部分我只保留了Spark Streaming消费Kafka里的订单支付事件用于大屏上的今日实时GMV和当前热销Top3两个模块。其他指标都走离线。这个取舍既让大屏有直播感又不至于把实时链路运维成本拉太高。2.4 可视化选型商业BI和FlaskECharts怎么选可视化层我最终选了Flask加ECharts而不是直接上帆软或Tableau。核心原因有三个大屏需要高度定制商业BI虽然拖拽方便但复杂布局和自定义交互反而受限。毕设和项目演示场景下自己写接口能完整展示后端能力商业BI会把亮点遮住。Flask加ECharts没有License成本部署就是一个Python服务加静态资源简单直接。组件职责选型理由Hadoop HDFS分布式文件存储存原始日志和中间结果扩展性好Hive离线数仓分层建模清晰支持SQL团队上手快Spark数据清洗/ETL处理速度快代码表达力强Kafka消息缓冲应对大促峰值流量削峰填谷MySQL结果存储存储ADS结果和维表查询快FlaskAPI服务轻量、灵活适合快速开发ECharts数据可视化图表丰富社区资源多适合定制大屏3. 数据接入与清洗链路从订单日志到可用数据3.1 业务数据源梳理线上、门店、会员、广告动工前先把数据源盘清楚。我整理的典型化妆品销售系统数据源包括线上订单表订单号、SKU、数量、实付金额、优惠券分摊、支付时间、渠道来源。线下POS表小票号、商品条码、数量、成交金额、开单柜员、专柜编号。会员CRM会员ID、手机号、注册时间、等级、生日、最近购买时间。广告投放表活动ID、渠道、曝光量、点击量、投放费用、投放日期。商品主数据SKU、品牌、品类、功效标签、吊牌价、成本价、上市日期。这块最容易忽略的是商品主数据。化妆品行业一个SKU往往有多个别称比如线上叫小黑瓶精华肌底液线下小票里可能写精华肌底液50ml如果不做SKU映射后续按品牌/品类聚合就乱了。我建议在清洗层之前先做一张SKU映射维表把各渠道的商品名称统一成标准SKU_ID。3.2 Flume采集与Kafka缓冲的真实取舍订单日志量级如果只有每天几十万条Flume直接进HDFS完全够用不需要引入Kafka。我最初的项目里就没有KafkaFlume配置taildir source监听应用日志落到HDFS按日期分区agent.sources r1 agent.channels c1 agent.sinks k1 agent.sources.r1.type taildir agent.sources.r1.filegroups f1 agent.sources.r1.filegroups.f1 /data/logs/order agent.sources.r1.filegroups.f1.fileSuffix .log agent.channels.c1.type memory agent.channels.c1.capacity 20000 agent.sinks.k1.type hdfs agent.sinks.k1.hdfs.path hdfs://node01:9000/dw/ods/order_log/%Y%m%d agent.sinks.k1.hdfs.fileType DataStream agent.sinks.k1.hdfs.rollInterval 3600这套配置在小规模场景下跑得很稳。但如果你预期业务有秒杀大促、峰值流量是平时的十倍那Flume直接写HDFS会在高峰期出现背压造成日志积压。这时候就需要在前面加一层Kafka做缓冲Flume或Canal只管往Kafka丢Spark Streaming再按能力拉取。判断标准很简单日均日志量级在百万以下直接Flume要扛大促峰值加Kafka。2025年了不用为了技术栈完整硬塞组件符合实际规模才是对的。3.3 Spark清洗规则重复订单、取消订单、优惠券拆分清洗这一层是整个数仓质量的关键。写Spark代码时我定了几条必须处理的规则过滤取消/退款订单order_status为CANCELLED或REFUNDED的直接剔除。去重按订单号、SKU、支付时间三个字段组合去重避免日志重复上报。金额修正实付金额必须大于0优惠券分摊必须等于订单优惠总额。时间标准化把各渠道的字符串时间统一成yyyy-MM-dd HH:mm:ss并转换成东八区。SKU映射通过维表把各渠道商品名转换成标准SKU_ID。核心清洗代码大致长这样from pyspark.sql import functions as F df spark.read.option(header, True).csv(hdfs://node01:9000/dw/ods/order_log/) df_clean ( df.filter(F.col(order_status) ! CANCELLED) .filter(F.col(order_status) ! REFUNDED) .filter(F.col(pay_amount) 0) .dropDuplicates([order_no, sku_id, pay_time]) .withColumn(pay_ts, F.unix_timestamp(F.col(pay_time), yyyy-MM-dd HH:mm:ss)) .withColumn(dt, F.from_unixtime(pay_ts, yyyy-MM-dd)) ) df_result df_clean.join(sku_dim, onsku_id, howleft) df_result.write.mode(overwrite).partitionBy(dt).format(orc).saveAsTable(dwd_order_detail)这段代码有几个细节要注意。dropDuplicates一定要放在过滤取消订单之后否则可能出现同一笔订单既被记录为有效又被记录为取消去重时留下错误记录。时间字段最好转成Unix时间戳后续做周期计算更灵活。partitionBy(dt)是必须的没有分区的话每次都全表覆盖数据量大了根本扛不住。3.4 数据质量校验与异常告警清洗完不等于数据没问题我在项目里加了一个质量校验环节用一张ods_data_check表记录每天的检查结果规则包括订单量波动今日订单量比前7日均值低或高超过30%时触发告警。空值率关键字段订单号、SKU、支付时间空值率超过0.1%时触发告警。金额异常存在负金额、单均金额高于品类均值10倍时触发告警。渠道缺失线上订单渠道字段为空的比例超过5%时触发告警。校验逻辑可以用Spark每天跑一次把结果写入MySQL通过大屏上的数据质量模块展示。别小看这一步很多系统死就死在数据错了没人知道。有一次大促活动结束后活动ID没有同步到线上订单表导致活动效果统计全部归零如果只看总量完全发现不了正是校验规则把问题暴露出来的。4. Hive数仓建模化妆品品类如何分层落表4.1 ODS、DWD、DWS、ADS四层设计数仓建模遵循经典分层每一层的职责很清晰ODS层原样保存采集进来的日志、文件、业务库快照。这一层不做事后修改保留最原始的凭证。DWD层清洗明细层。在ODS基础上完成去重、标准化、SKU映射以订单商品行粒度存储。DWS层汇总层。按天、品牌、品类、渠道、价格带等维度预聚合指标。ADS层应用层。面向可视化大屏和报表输出表结构贴合页面需求。四层结构看着麻烦但它最大好处是ODS到DWD是数据质量修复DWD到DWS是指标口径固化ADS是结果输出。层与层之间职责分离出问题能快速定位到是哪一层逻辑写错。4.2 DWD明细表的粒度选择与维度退化DWD订单明细表我最终选择了订单商品行粒度也就是一个订单里买了几种商品就拆成几行。这样既能算订单号维度的整体GMV也能算SKU维度的销量还能通过订单号关联回到订单表算连带率。建表时我做了维度退化把品牌、品类、渠道、价格带、活动ID、会员等级这些字段直接复制到明细表里而不是通过外键再去关联维表。原因很简单化妆品销售明细的查询基本都是多维度交叉分析如果每次计算都去join维表性能和复杂度都会指数级上升。维度退化会带来一些存储冗余但换来了查询便利这笔账划算。4.3 DWS与ADS核心指标连带率、复购率、价格带DWS层我建了一张dws_sale_day_dim表粒度是日期品牌品类渠道价格带存储GMV、订单量、销量、买家数、新客数等指标。ADS层再基于它做应用口径计算。举两个关键指标的计算方式。连带率客单价件数比较简单select dt, sum(sku_num) * 1.0 / count(distinct order_no) as link_rate, sum(gmv) * 1.0 / count(distinct order_no) as avg_order_value from dwd_order_detail where dt 2025-03-01 group by dt;复购率稍微复杂我按过去30天内有多次购买的用户占比来算select dt, count(distinct user_id) as buy_users, count(distinct if(buy_cnt 2, user_id, null)) as rebuy_users, count(distinct if(buy_cnt 2, user_id, null)) * 1.0 / nullif(count(distinct user_id), 0) as repurchase_rate from ( select dt, user_id, count(distinct order_no) as buy_cnt from dwd_order_detail where dt between date_add(2025-03-01, -29) and 2025-03-01 group by dt, user_id ) t group by dt;价格带分布则是把商品吊牌价按低端、大众、中端、高端、奢侈五档分桶再统计各档的SKU数和GMV占比。这个指标对化妆品特别重要品牌方需要看清自己的主力价格带在哪、高端线有没有增长空间。4.4 分区策略与快表处理Hive表全部按dt做日期分区这是离线数仓的标配。但有两个细节容易被忽略第一DWD明细表不要用insert overwrite table ... select全表覆盖要用insert overwrite table ... partition(dt2025-03-01)只覆盖指定分区。这样可以保证某天数据出问题时只需要重跑那一个分区不影响历史数据。第二会员维表属于缓慢变化维我直接用每日全量快照表每天一份完整会员数据。化妆品会员几百万条日全量也就几百MB存储压力不大但好处是回溯历史某天的会员等级时特别方便。维表快照加明细分区是支撑可重跑、可回溯的核心。5. 可视化大屏与报表FlaskECharts落地实践5.1 大屏布局逻辑与核心看点可视化层是给业务方看的我的布局原则是第一屏放结论第二屏放路径。顶部一排核心KPI卡片今日GMV、订单量、连带率、复购率。中间左侧放GMV趋势折线图近30日中间右侧放品牌销售排行柱状图。下面左侧放价格带分布环形图右侧放渠道贡献占比饼图最底部放Top10热销SKU表格。大屏拿到BI数据再配图核心要看的是整体卖得怎么样、什么在涨、什么在跌、钱是从哪个渠道哪个品类来的。ECharts的联动和下钻能力可以做到点击某个品牌后其他图表自动过滤为该品牌的数据。我通过Flask接口的brand参数实现前端用myChart.on(click, callback)触发。5.2 Flask接口设计与参数校验Flask层我设计了一组以/api/开头的JSON接口前端统一通过fetch调用/api/overview?dt2025-03-01核心KPI卡片数据。/api/trend?dt2025-03-01days30近30日GMV趋势。/api/category_rank?dt2025-03-01limit10品牌/品类排行。/api/price_band?dt2025-03-01价格带分布。/api/channel?dt2025-03-01渠道贡献。代码写起来很简单import json from flask import Flask, request, jsonify from db import query_all app Flask(__name__) app.route(/api/overview) def api_overview(): dt request.args.get(dt, 2025-03-01) sql select * from ads_sale_overview where dt %s rows query_all(sql, dt) return jsonify({code: 0, data: rows})接口层要注意两点一是参数必须做校验dt格式要是YYYY-MM-DD否则SQL注入风险和大屏报错都会找上门二是接口返回的数据结构要稳定别一会儿返回list一会儿返回dict前端看着看着就疯了。我在项目里统一用{code, data, msg}格式前端据此处理异常。5.3 ECharts常用图表在销售场景里的适配ECharts图表本身是通用的但放在销售系统里需要专门调口径折线图显示GMV趋势时我额外加了移动平均线避免周末自然波动干扰判断。柱状图品牌排行用GMV 订单量双Y轴一轴是销售额一轴是销量避免只看销售额忽略低价品走量。环形图价格带分布占比图例里要带GMV占比/订单量占比两者差异能反映客单结构。漏斗图从商品曝光、点击、加购、支付到复购的转化路径这是化妆品营销最关心的漏斗。配色上尽量用品牌主题色不要用默认的五颜六色大屏是给管理层盯一整天的视觉干扰越少越好。5.4 性能优化接口聚合与前端缓存大屏打开时前端会一次性请求五六个接口。如果每个接口都实时去MySQL做多表聚合页面加载会明显卡顿。我的做法是两层优化第一在ADS层把大屏需要的指标提前聚合好接口只是简单查询不做复杂计算。比如近30日趋势在ADS里就有一张按天、按品牌、按渠道汇总好的表接口直接select就行。第二引入Redis缓存。当天数据一旦生成就不会变化所以接口层加了一个很轻的缓存from redis import Redis cache Redis(host127.0.0.1, port6379, db0) CACHE_EXPIRE 60 * 60 * 12 app.route(/api/trend) def api_trend(): key api_trend:{}:{}.format(request.args.get(dt, ), request.args.get(days, 30)) cached cache.get(key) if cached: return cached # 查询MySQL... cache.setex(key, CACHE_EXPIRE, json.dumps(data)) return jsonify(data)加了这一层之后大屏反复刷新基本不再打到MySQL接口响应从几百毫秒降到几十毫秒。6. 集群部署与运维排障上线后的真实问题6.1 单机/分布式部署选择与资源估算部署方案取决于使用场景。毕设或个人学习一台16G内存的服务器跑Hadoop伪分布式加Spark local模式完全够用企业项目或者演示环境建议至少3个节点每个节点32G内存起步namenode和resourcemanager单独部署。磁盘容量估算有一个很实用的公式所需存储 日增数据量 × 压缩比 × 副本数 × 保存天数比如每天新增订单日志20GBORC加Snappy压缩后约4GB压缩比0.2副本数默认3保存90天算出来大约是108GB。看起来不大但如果你还存了会员快照、商品快照、中间计算结果实际占用往往会翻一倍。做规划时至少留1.5倍余量。6.2 常见故障Spark OOM、数据倾斜、Hive小文件上线半年我踩过最典型的三个坑。第一个是Spark OOM。原因是我在清洗时把所有渠道数据统一collect()到Driver端数据量一大直接内存溢出。后来改成全流程用DataFrame算子不再collect大集合只在最后结果集collect问题解决。记住一个原则清洗链路里不要随便把分布式数据集拉回Driver。第二个是数据倾斜。某品牌在促销期间订单量占全站60%按品牌聚合时一个Reduce任务要处理全站一大半数据跑得慢还容易失败。解决办法是加盐打散第一次按brand_id _ rand()分组做局部聚合第二次按真实品牌聚合。代码不复杂但排查过程很费劲状态一直卡在99%点开Spark UI发现某个task处理的数据量是其他task的几十倍。第三个是Hive小文件。Flume写HDFS时默认按时间滚动很容易生成大量几十KB的小文件导致Spark读取时任务数爆炸。我在Hive表上设置了小文件合并参数同时让Flume按数据量滚动文件agent.sinks.k1.hdfs.rollInterval 0 agent.sinks.k1.hdfs.rollSize 134217728 agent.sinks.k1.hdfs.rollCount 0这样每个文件大约128MB读取效率明显提升。6.3 数据调度与重跑策略调度我用的是海豚调度DolphinScheduler也可以换Azkaban或Airflow。调度的核心不是定时触发而是依赖管理。每天的作业流应该是Flume采集 → Spark清洗 → Hive DWD建模 → Hive DWS聚合 → 导出MySQL → 刷新Redis缓存每一个环节失败后续环节都不能启动。海豚调度里直接配置任务依赖关系前序任务失败会自动阻塞后续任务。这个设计保证了大屏上的数据永远是完整链路的产物不会出现清洗失败但汇总还在跑的脏数据状态。重跑策略也要提前设计好。因为Hive是分区表重跑某个日期只需要两步先删除目标分区再执行insert overwrite重新写入该分区。MySQL的ADS表同样按dt建表重跑前先delete当天数据。整个过程保持幂等重复跑N次结果都一样。6.4 做完这套系统我最想强调的几件事现在回过头看这套基于大数据的化妆品销售系统最值钱的地方不在用了多少组件而在于把业务问题→数据链路→指标口径→可视化展示这条路彻底走通了。很多人做大数据项目喜欢先选技术栈再想业务场景结果就是一堆组件叠上去实际回答不了业务方的任何一个问题。如果你正准备做类似项目我的建议是先把指标口径和表结构设计出来再谈Hadoop、Spark、Flask这些组件怎么用。技术是工具数据能解决业务问题才是目的。2019年我第一次搭这个系统时没有加Redis缓存也没有Kafka前端接口一慢就只能干等。后来逐步优化加上缓存和削峰才发现这些逼着你去思考数据量大了怎么办的场景才是大数据项目真正的成长点。希望这篇分享能让你少走一点我当年走过的弯路。