手机销量数据可视化平台:从爬虫到ECharts的完整实践
手机市场这两年有个很有意思的现象价格战打得很凶但消费者反而更纠结了。打开电商平台搜手机筛选项多得让人头疼价格区间、品牌、内存、摄像头每一项都是一堆选项最后往往还是不知道买哪台。我去年做数据分析项目时正好接了一个手机品类市场研究的活儿需求很明确把分散在各电商平台的手机销量数据汇聚起来按价位段做可视化分析再做成一个能辅助选型的平台。折腾了两个月把这个项目完整落地了今天把整个过程拆开来聊聊。这个平台的核心简单说就是一条数据流水线最上游是爬虫去几个主流电商平台抓手机类目的销量、价格、评价数、上市时间这些字段中间是数据处理和存储环节清洗、去重、归一化之后落到 MySQL热点查询走 Redis再往上是一个基于 Flask 的后端接口层最后是浏览器端的可视化大屏用 ECharts 绘制。用户打开页面能看到价位段销量分布、品牌热度、单品排行这些图表还能输入预算让平台基于销量和口碑数据给出推荐机型。做这个项目最大的感受是技术难点其实不在画图而在数据能不能扎实地支撑起这些图表。1. 项目整体设计思路与方案选型1.1 这个平台到底要解决什么问题先从一个真实的场景说起。假设一个人想买 2500-4000 元的手机他在电商平台搜索手机结果页会展示几十款不同品牌、不同规格的机型。平台默认按综合排序但这个综合权重包括了广告投放费用、店铺评分、促销力度真实卖得好的机型不一定排在最前面。用户翻完前几页大概率会陷入选择困难。更深层的问题在于单个平台的销量数据只是局部真相。同一款手机在 A 平台卖爆了在 B 平台可能很一般线上线下的价格体系又不同。想相对客观地判断某个价位段哪些机器卖得不错就需要跨平台的数据融合需要统一口径去比较。所以这个平台的第一个核心需求是把分散的销量数据汇聚、清洗、标准化然后用可视化方式呈现让用户能在几秒内理解某个价位段的市场格局。第二个需求才是智能选型推荐——在可视化数据基础上把多维指标综合成一个可比较的分数降低用户的决策成本。两个需求是递进关系没有可靠的可视化数据推荐结果就没有公信力没有推荐模块平台也只是个普通的数据看板少了选型这个落点。1.2 可视化在整个项目中的核心价值很多人觉得可视化就是画几张好看的图这个项目恰恰相反可视化是核心逻辑的一部分不是锦上添花的展示层。手机销量数据的原始形态是没法直接支撑决策的。一张存了几万条记录的表用户不会看也没法看如果只用文字列一个某价位段销量 TOP5的清单用户也难以判断结论的可信度——他看不到这个 TOP5 和整个市场的关系。可视图表的价值在于把数据关系变成空间关系柱状图的高度差让用户一眼看出哪个价位段是红海、哪个是蓝海折线图的斜率让用户直观感受到畅销机型的上升期和衰退期散点图的价格-销量分布能帮用户识别高价高销量的标杆机型也能看出高价低销量的冷门机型。这种认知效率是表格和文本远远达不到的。在数据验证阶段可视化还帮我发现了很多数据质量问题。比如某平台个别品类的销量字段偶尔会返回异常值如果只看数据库表根本发现不了但可视化图上会出现一根异常高的柱子一眼就能看出来再回去查数据源问题就定位了。可以说可视化从一开始就贯穿了数据质量校验的整个流程。1.3 技术栈选型的对比与考量技术选型我做过几轮对比直接说结论和理由。数据采集这块我选的是 Python requests BeautifulSoup。Python 是数据类项目的事实标准爬虫生态成熟requests 加 BeautifulSoup 适合中等规模的页面解析Scrapy 适合需要大规模并发抓取的场景但这个项目的手机品类 SKU 数量在几千到一万左右单平台全量抓取也就几万个页面请求用 requests 加线程池足够Scrapy 有点重。解析 CSV 或 JSON 接口时 BeautifulSoup 也够用如果遇到复杂嵌套页面我会临时用 lxml 顶上。数据存储选的是 MySQL Redis。MySQL 负责主数据存储手机型号信息、销量明细、价格历史都落 MySQLRedis 用来缓存可视化接口的热点查询结果。为什么不用 MongoDB因为数据模型很规整字段基本固定关系型数据库用起来更顺手而且后续做按品牌、价位段、时间聚合查询SQL 比 MapReduce 方便太多。Redis 缓存的是聚合查询结果比如各价位段销量占比近6个月各品牌销量趋势这些查询如果每次实时跑 MySQL大表聚合延迟会到一两秒可视化交互根本没法接受。后端接口选了 Flask。Flask 轻量、生态成熟适合快速迭代。项目没有超高的并发诉求用 gunicorn 部署多进程就能撑住。我也考虑过 FastAPI但项目里没有太多异步场景团队对 Flask 更熟踩坑成本低就定了 Flask。前端可视化选了 ECharts。ECharts 是目前适配度最高的选择文档全、示例丰富、中文社区活跃做数据大屏和仪表盘非常顺手。开发阶段我用 pyecharts 快速生成 HTML 原型验证视觉上线版本用原生 ECharts 手写配置项方便做定制交互。AntV 在设计规范上也不错但中文社区资源和示例丰富度确实不如 EChartsD3.js 虽然灵活度最高但开发成本太大不适合这个项目。技术环节选型备选方案选择理由数据采集Python requests BeautifulSoupScrapy数据规模适中轻量方案够用数据存储MySQLMongoDB数据模型规整SQL 聚合方便缓存Redis无热点聚合查询需毫秒级响应后端接口Flask gunicornFastAPI无高并发诉求团队熟悉可视化EChartsAntV G2Plot、D3.js生态成熟、交互友好、文档全2. 数据采集与清洗处理2.1 爬虫模块的分阶段设计爬虫是整个项目里最耗时、最容易翻车的环节。刚开始我做了一个错误的决定直接把目标锁定在全平台全品类的手机数据想一次性把所有平台都拿下。结果发现不同平台的反爬策略差异极大有的平台需要登录态有的对请求频率极其敏感有的数据藏在接口返回的 JSON 里有的只能解析 HTML。贪多嚼不烂第一周基本在反复调试各种异常。后来我调整了策略先做单平台的数据抓取验证把一个平台的手机类目全部跑通再逐步扩展到其他平台。这个策略被证明是高效的——打通第一个平台后后面每个平台的适配时间压缩到了三四天。单个平台的抓取流程是这样的先分析页面结构确定列表页的分页规则找到列表项里关键字段商品 ID、标题、价格、销量、评价数的 DOM 位置或 JSON 字段名写解析逻辑然后设置合理的抓取频率每请求一页睡 1-2 秒加上随机 User-Agent 和简单的请求头伪装基本上能稳住最后把抓到的数据写入临时存储跑一轮全量后检查字段完整率和正确率没问题再进入下一步。这里有一个我特别想强调的经验电商列表页上的销量数字经常不是真实销量而是30天付款人数累计评价数这类近似值不同平台的口径还不一样。如果要做跨平台比较必须统一口径。我在清洗阶段统一换算成近30天销量估计值在表里加一个置信度字段标注数据的可信程度——这一步是后续所有分析能否站住脚的基础。2.2 数据字段设计与型号解析落到 MySQL 的手机商品表我设计的核心字段大致是这样字段名类型说明idBIGINT主键platformVARCHAR(20)平台标识product_idVARCHAR(64)平台商品IDtitleVARCHAR(255)商品全标题brandVARCHAR(50)品牌从标题解析modelVARCHAR(100)型号从标题解析priceDECIMAL(10,2)当前价格original_priceDECIMAL(10,2)划线价或参考价sales_volumeINT近30天销量估计值comment_countINT评价数scoreDECIMAL(3,2)商品评分publish_dateDATE上市时间fetch_timeDATETIME抓取时间categoryVARCHAR(50)类目最需要花心思的是品牌和型号的提取。手机标题五花八门例如【官方】华为 Mate 60 Pro 12GB512GB 雅丹黑 5G手机有的标题还会带官方标配正品限时优惠这些词。如果简单用字符串分割型号字段很容易出错。我写了一套基于正则加关键词库的解析逻辑先匹配品牌词库华为、苹果、小米、OPPO、vivo、荣耀、三星、一加、真我、魅族等再在品牌之后提取型号关键词同时把官方标配正品限时这类噪音词过滤掉。最开始我用简单的字符串前缀匹配结果 30% 以上的记录型号是空的或错乱的改成正则加过滤词后准确率才到 95% 以上。型号解析这一步非常关键如果做不好后面所有价位段分析、品牌维度分析都会失真。我建议在开发阶段就准备一份品牌关键词库并且定期根据新出现的品牌和子品牌更新。2.3 数据清洗、去重与聚合策略重复数据是另一个大坑。同一个手机型号在同一个平台会对应多个 SKU比如不同颜色、不同存储版本价格和销量都不一样。如果直接按商品 ID 去重同一个型号会被拆成好几条记录价位段分析必然混乱如果完全不区分销量又会重复计算。我的处理办法是以品牌 型号 存储版本为粒度做聚合把同型号不同颜色的 SKU 销量相加价格按销量加权平均评价数相加。这样做既不会漏算也不会重算。去重还有一个容易被忽略的细节很多平台会针对同一款手机设置多个店铺的商品页商品 ID 不同标题几乎一样。通过品牌 型号 存储版本的聚合逻辑这类重复项也能自动合并效果很好。清洗阶段我还要做几件标准化的事价格字段里的¥符号和逗号全部去掉转成 DECIMAL 类型销量字段的万亿单位换算成整数空值和异常值打标记比如销量为 0 但评价数几千的记录说明销量数据可能缺失这条记录在聚合时要特殊处理。字段类型上价格必须用 DECIMAL 而不是 FLOAT否则浮点数运算会出现精度失真这个问题在后续算均价、加权价的时候会暴露得非常明显。2.4 数据存储与缓存设计在表设计上可视化接口经常按月、价位段做聚合查询所以在 price 和 fetch_time 上建联合索引很重要。没有索引的时候一个简单的各价位段销量分布查询在几十万行的表上要跑两三秒加上联合索引后单次聚合查询能压到几百毫秒以内。Redis 缓存我设了两种。一是固定聚合维度的查询结果比如各价位段销量占比近6个月各品牌销量趋势这些数据更新周期是每天一次缓存过期时间就设为 24 小时每天凌晨定时任务更新数据时主动淘汰缓存。二是用户自定义选型条件的推荐结果缓存 10 分钟避免相同条件的用户反复触发实时计算。上缓存之后可视化接口的平均响应时间从 1.8 秒降到了 200 毫秒左右整个平台的交互流畅度完全不一样了。3. 可视化方案选型与核心实现3.1 为什么是 ECharts 而不是其他框架做可视化方案对比时我认真考察了三条路线ECharts、AntV G2Plot、D3.js。D3.js 的灵活度最高图表几乎什么都能做但开发成本太大数据绑定和 SVG 绘制的学习曲线很陡用它做一整套大屏加常规图表周期至少翻倍不划算。AntV 在移动端适配和设计规范化上做得不错但中文社区和示例丰富度不如 ECharts遇到冷门配置问题能搜到的解决方案少。ECharts 的优势正好命中这个项目的需求快速落地、交互友好、文档全而且 pyecharts 能直接生成 Python 后端可用的图表配置原型验证阶段效率极高。还有一个很实际的因素项目的用户主要是运营和市场人员他们偶尔需要自己配图表看数据。ECharts 在国内的普及度最高团队里随便一个人都能改配置维护成本最低。所以最终选 ECharts 是一个综合考虑开发效率、生态成熟度和维护成本的决定。3.2 核心图表的业务定义与配置要点这个平台最终沉淀了五类核心图表每一类解决的业务问题不同。第一张是价位段销量分布柱状图。横轴是价位段0-1500、1500-2500、2500-4000、4000-6000、6000纵轴是该价位段的汇总销量。这是整个平台最核心的一张图用户第一眼看到的就是它。柱状图的高度差异能直接反映市场结构是千元机主导还是中高端机占比更大。ECharts 配置上没什么难点但要注意价位段划分要稳定不要频繁调整否则历史趋势对比会乱。第二张是品牌-价位段热力图。横轴是品牌纵轴是价位段颜色深浅表示销量。这张图能回答华为在高端价位段的销量表现如何小米在 2000-3000 价位段是否有优势这类问题。热力图用 ECharts 的 heatmap 系列坐标轴标签如果太多会重叠需要设置合适的 grid 留白。颜色渐变建议用浅色到深色的单色渐变销量用蓝色这样视觉上更干净。第三张是月度销量趋势折线图。按月份展示不同价位段销量的变化趋势一条线一个价位段。这张图能清楚看到某些价位段在某个月份出现明显的销量脉冲通常对应新机发布或大促节点。ECharts 配置时要注意数据集结构我用的数据结构是 { date: 2024-06, segment: 2500-4000, sales: 12345 } 这种长表格式方便做多系列映射。第四张是价格-销量散点图。横轴是价格纵轴是月销量每个点是一个机型。这是信息量最大的一张图左上角是低价高销量的大众机型右下角是高价低销量的冷门高端机中间分布着不同性价比区间的产品。我在图上加了一条线性趋势线帮助用户理解市场里价格与销量的总体关系。散点图的数据点数量可能达到几千个直接渲染会卡我在服务端先做了简单抽稀数据量控制在一千点以内tooltip 再显示完整信息。第五张是单品销量排行榜。TOP30 榜单用条形图横向展示数值标签带销量和均价。用户点击某一条可以下钻到机型详情页看到它近 6 个月的销量趋势和历史价格曲线。排行榜的 ECharts 配置要特别注意 yAxis 的 category 数据顺序从大到小排列否则图形跟榜单名次对应不上。3.3 大屏布局、交互与性能优化大屏布局我分了四个区域顶部放标题和核心 KPI——总销量、总在售机型数、平均价格、价位段数量中间主视觉区放价位段销量分布柱状图和品牌-价位段热力图右侧放排行榜和散点图底部是一条可横向拖动的月度趋势折线图。交互设计做了三个核心动作。第一点击柱状图的某个价位段右侧排行榜和底部趋势图会联动刷新只显示该价位段的数据第二鼠标悬停散点图的数据点显示机型详情浮窗第三顶部提供一个全平台品牌下拉框选择品牌后全屏所有图表统一过滤。大屏适配是前端容易忽略的一个点。用户可能是 1080p 显示器也可能是 2K 甚至 4K 大屏如果固定像素宽高会出现变形或白边。我用 rem vw/vh 自适应方案ECharts 图表在容器尺寸变化时调用 resize() 方法。另外大屏通常需要 7x24 小时挂着我加了一套数据定时刷新机制每 5 分钟拉一次最新数据保证展示不过时。性能优化方面最重要的一点是 ECharts 实例的生命周期管理。长时间挂机时如果频繁 setOption 而不清理内存会缓慢增长。我的方案是每小时销毁一次所有图表实例并重建实测内存曲线明显平稳。还有一个常见坑是切换筛选条件时直接 setOption旧的 series 偶尔会残留解决办法是先 clear() 再 setOption或者用 notMerge 参数强制覆盖。4. 智能选型推荐逻辑4.1 从可视化到推荐数据如何支撑决策有人会问有了可视化为什么还要做智能选型我的理解是可视化解决的是看懂市场的问题智能选型解决的是怎么选的问题两者互补。推荐逻辑本质上是对可视化数据的再加工。用户在页面上输入预算区间和可选条件比如品牌偏好、是否接受二线品牌、是否偏好新机型后端从数据库里取该价位段内所有机型按销量、评价数、评分、上市时长这几个维度加权算出一个推荐分数按分数排序返回前五名。前端把每个推荐机型的关键指标做成一个小卡片附上一个迷你柱状图展示它的销量在同价位段的排名位置。这就是可解释推荐的落地方式——用户不仅看到一个结果还能看到这个结果是怎么来的。4.2 推荐分数的计算与权重设计推荐分数的计算是这个模块里我反复调得最多的地方。一开始我直接按销量排名结果发现结果严重偏向低价机型——因为低价位段的绝对销量天然更高销量排名靠前的总是千元机。后来我把销量指标换成同价位段内的相对销量百分位即某机型销量在该价位段所有机型中的百分位排名。这个指标天然是相对值跨价位段可以直接比较。再和评价数百分位、评分、上市时长做加权平均。权重经过几轮调整最后稳定在一组比较合理的数据上销量百分位0.4评价数百分位0.2商品评分0.2上市时长得分0.2上市时长得分的计算规则是上市时间在 12 个月内得 1 分12-24 个月得 0.7 分24-36 个月得 0.4 分36 个月以上得 0.1 分。这个设计的逻辑是手机更新换代快上市太久的机型虽然销量可能不错但考虑到后续系统更新、配件供应和售后支持推荐价值会打折扣。这个权重也做成了可配置项运营人员可以在后台调整。一个值得提醒的地方推荐分数只反映数据层面的市场表现不等于绝对购买建议。我在产品说明里也标注了推荐结果基于公开销量数据供参考不做绝对背书。这样可以避免一些预期上的偏差。4.3 可解释性与数据反哺推荐结果如果没有解释用户很难信任。我做了两层解释。第一层是为什么推荐这款在推荐卡片上用文字展示它的销量排名、评价数、评分在同类机型中的位置。第二层是可视化佐证在推荐机型详情页嵌入该机型的销量趋势折线图和价格历史曲线让用户自己验证结论。折线图上升得漂亮、价格曲线平稳用户自然会对推荐更有信心。这个设计让可视化数据和推荐算法互相反哺。可视化数据指导推荐算法的输出用户点击推荐机型的行为数据反过来也能给运营提供洞察。我在项目里加了一个简单的埋点统计记录用户点击了哪个推荐机型再结合该机型的销量排名能看到用户偏好和市场热度的偏差。比如某个机型销量排名不高但被频繁点击说明用户对它有特定关注点这个信息可以反馈给选品运营也可以用于后续的推荐算法调优。5. 常见问题与排查技巧实录5.1 爬虫篇反爬与数据字段异常爬虫模块的问题最多。常见的反爬手段包括请求频率检测、User-Agent 检测、IP 频率限制、登录验证码、字体反爬把关键数字做成自定义字体页面显示正常但源码里是乱码。我处理最快见效的方案是随机 User-Agent 加请求间隔成本低且多数情况下有效。遇到频率限制时尽量在夜间低峰期抓取再配合多线程的必要退避。IP 层面的限制优先通过错峰抓取和请求节流来规避减少对代理资源的依赖。另一个特别关键的坑是数据字段形态的稳定性。不同时刻抓取同一字段可能有时是字符串、有时是数字有时带单位“万”空值可能是 null、空字符串或 0。这种脏数据在清洗层做统一类型转换还不够最有效的办法是加数据质量告警机制如果某天某个核心字段的缺失率超过 10%触发告警说明页面结构可能改了需要人工检查爬虫脚本。没有这个机制数据出了问题往往两三天后才发现严重影响分析结论。5.2 MySQL 与 Redis 的典型问题MySQL 大表聚合慢的问题前面提到过通过联合索引加 Redis 缓存解决了。但 Redis 还有一个很隐蔽的坑缓存穿透。用户如果输入一个非常冷门的价位段比如 0-500 元但平台上几乎没有这个价位段的手机数据查询在缓存和 MySQL 里都没有结果。如果这个查询被高并发触发每个请求都会打到数据库数据库压力会瞬间上升。我的处理是加空值缓存即使查询结果为空也在 Redis 里缓存一个空列表过期时间设 5 分钟就能挡住大部分穿透请求。字段类型设计上的坑也值得再提一次。价格字段如果图省事用 FLOAT经过多次计算后会出现 0.1 0.2 不等于 0.3 这种精度问题在计算加权均价、价格区间分布时会非常蛋疼。我最后统一把价格字段改成了 DECIMAL(10,2)才彻底根治。5.3 可视化前端的常见坑前端的坑集中在 ECharts 的使用细节上。一个高频问题是图表在容器尺寸变化时没有调用 resize()导致大屏在切换分辨率时图表变形。解决办法是把 resize 绑定到 window 的 resize 事件上并且做防抖处理。另一个是数据量大的渲染性能问题。几千个散点一次性 setOption浏览器会卡顿散点图的交互会掉帧。我的方案是服务端抽稀数据点数量控制到一千以内同时开启 sampling 配置项。抽稀后 tooltip 里再异步请求完整数据体验上几乎没有损失。还有个交互上的细节ECharts 的 tooltip 在悬停时默认显示所有 series如果同一个容器里有多个系列tooltip 会非常拥挤。我通过配置 tooltip 的 formatter 函数只显示当前悬停的系列数据同时加上单位说明这个体验细节很多项目会忽略但对用户来说感知很强。最后再分享一个运营层面的经验大屏上线后一定要有一个数据血缘说明文档。记录每个图表对应哪个接口、哪个数据源表、哪个聚合SQL。项目做久了人员可能变动如果数据口径说不清后续维护的同事会非常痛苦。我在项目交付时专门做了一份这样的文档虽然写起来花时间但后面几次迭代调试都靠它救命。这个项目做下来我自己购机的时候都养成习惯了——先打开平台看一眼同价位段的销量分布和趋势再决定重点看哪几款。如果要说一个最大的心得那就是数据可视化项目的成败很多时候不取决于图表画得多漂亮而取决于数据链路是否扎实。爬虫能不能稳定拿到可信的数据清洗层能不能处理好平台间的口径差异推荐逻辑能不能把多维度信息融合成人类能理解的结论——这些才是真正决定项目价值的地方。可视化只是最后那一层翻译官把复杂的数据关系翻译成用户能够快速理解的视觉语言。数据扎实了翻译出来的内容才有意义。