Flask+Selenium+机器学习:电商数据可视化与销量预测实战
要说这套毕设题目很多同学打开第一眼会有点懵Python电商数据可视化、销量预测、Flask、Selenium、机器学习、Hadoop一口气全塞在一个系统里听着就头大。但冷静下来拆开看它其实是一条特别经典的“电商数据链路”先用 Selenium 把商品数据和销量数据抓下来清洗后交给 Flask 做后端服务再用图表把分析结果可视化出来最后用机器学习模型对未来的销量做预测Hadoop 则负责在数据量变大时兜底做离线加工。这套系统能解决的实际问题很明确——把“采集、分析、预测、展示”四件事串成一条流水线适合拿来当毕业设计、课程设计或者写进简历作为完整的项目经历。我在实际做这类项目时最大的感受是难点不在单个技术而在怎么把这些技术拧在一起。如果你的导师只看重“系统能跑、论文有东西写”那你更需要关注模块边界怎么划分、模型精度怎么合理、部署怎么不出幺蛾子。这篇文章我会从架构设计讲到底层实现细节再把我踩过的坑、排查过的问题全部倒出来尽量让你拿着就能往下做。1. 项目定位与整体架构设计1.1 一条完整链路比堆砌技术更重要一个电商数据分析预测系统最终要回答的无非三类问题卖得怎么样、为什么卖成这样、接下来怎么卖。对应到代码层面就是数据采集、指标分析、销量预测三个核心环节再加上一个展示层把结果抛到浏览器上。所以整个系统可以拆成四层采集层Selenium 抓取电商页面拿到商品标题、价格、评论数、销量、上架时间等原始数据。存储与加工层清洗、去重、统一格式写入 MySQL 或 CSV/Parquet如果数据量确实大就放到 HDFS 上做离线统计。分析预测层对销量做探索性分析提炼趋势和相关性训练机器学习模型预测未来销量。展示层Flask 提供接口前端用 ECharts 画折线图、柱状图、饼图、热力图。这里有个关键认知毕设的评分重点通常是“系统是否自洽”不是“用了多少新技术”。很多同学喜欢把 Hadoop、机器学习、可视化全堆上去但模块之间没有数据交换答辩一问就露馅。我做的时候会画一张数据流图每一步都写清楚输入是什么、输出到哪里后面写论文时直接复用这套逻辑省力很多。1.2 技术选型背后的取舍逻辑这套系统里每个组件都不是随便选的我把我选择时的考虑整理成一张表方便你做决策。模块首选方案核心原因备选方案Web 框架Flask轻量、灵活适合中小型系统接口开发快路由直观Django重、附加功能多、FastAPI异步友好但部分插件生态偏新动态数据采集Selenium直接驱动真实浏览器能处理 JS 渲染、Ajax 异步加载、模拟点击requests API 解析高效但容易被反爬卡住、Playwright新一代替代也可行数据存储MySQL结构化商品数据事务和查询成熟可视化取数方便SQLite数据量小可用、MongoDB字段不固定再考虑预测算法线性回归 随机森林可解释性好适合教学展示特征少也能出效果XGBoost、LightGBM、Prophet追求精度再上大数据组件Hadoop体现离线海量数据处理能力给系统加权重Spark速度更快但资源占用高、Pandas 数仓方案简单但不够“大数据”可视化ECharts图表类型全、交互好、中文文档多适合嵌入 Web 页面Highcharts商用授权需注意、Plotly交互强但体积大Flask 之所以在这个场景里比 Django 顺手是因为你不需要 Django 自带的 Admin 后台、ORM 权限体系那一整套东西。你只需要几十个路由把 JSON 数据吐给前端就行Flask 的轻量能让代码保持直白出问题也容易排。Selenium 则是因为电商页面大多是动态加载直接用 requests 拿到的 HTML 里可能连销量数字都看不到后面我会专门展开讲。2. 数据采集模块Selenium 的正确打开方式2.1 静态请求抓不到的数据用浏览器自动化补齐先说说为什么这个项目里必须用 Selenium。我见过很多同学先写 requests 脚本结果发现页面上商品的价格、销量、评价数全是异步加载出来的HTML 源文件里只有一个空的divAjax 请求地址又被加密或者加了签名校验硬解加密会消耗大量时间而且电商平台的风控也不是吃素的。Selenium 的思路是把整个浏览器“傀儡化”让 Chrome 真实地去加载页面、执行 JS、等待数据渲染最后再从 DOM 里取值。它的缺点是慢但数据抓得稳。实际使用中我建议在chromedriver里打开headless模式也就是无头浏览器这样不会弹窗打扰自己也节省内存from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) options.add_argument(--langzh-CN) driver webdriver.Chrome(optionsoptions) driver.get(https://example.com/product/123)这里有个细节--window-size一定要设置。很多电商页面会判断视口宽度宽度太小就只渲染移动端布局而移动端详情页的数据结构和 PC 端差别很大你按 PC 端的 XPath 去取元素就会扑空。我一开始没设置结果抓下来的全是“已售 100”这种缩略文案后来把窗口调成 1920x1080 才拿到完整销量数字。2.2 等待、滚动与懒加载控制页面渲染节奏Selenium 最常见的坑是“元素找不到”。大多数原因是页面还没加载完代码就急着去取节点。直接用time.sleep(3)当然能解但太浪费而且网络一波动就报废。我更推荐显式等待from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait WebDriverWait(driver, 10) element wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, div.sales-count)) )然后滚动问题。电商的商品列表页基本都有懒加载机制页面滚到底部才会继续加载下一批商品这跟热搜里那个“selenium 网页左右滑动”以及“selenium 左右滚动可见”是同一个痛点。解决方案就是反复执行滚轮操作driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(1.5)如果遇到横向滚动才能露出更多商品的情况就把window.scrollTo改成横向坐标比如driver.execute_script(window.scrollTo(document.body.scrollWidth, 0);)实测下来最稳的做法是“滚动 判断列表长度是否变化”如果滚动三次列表长度都不变就认为已经加载到底再开始解析不要傻傻等固定轮次。2.3 反爬应对与采集纪律采集时最招人恨的就是验证码和滑块。我强烈不建议去硬刚这些验证码因为突破成本高、容易触发更严格的风控而且作为学习项目没必要冒违规风险。我的策略是控制频率每抓一页随机休眠 2~5 秒不要用固定间隔更不要让脚本 24 小时连跑。随机 User-Agent 和 Cookie用fake_useragent库生成随机 UA登录态的 Cookie 要定期更新。失败重试加了超时和重试机制比如某个商品详情页连续请求 3 次失败就跳过记录到日志里。从合规和公共规范的角度我们这个系统用于课程设计和学习演示抓取量要控制在小规模、不影响目标站点正常服务的水平这点在论文的“数据采集说明”里一定要写清楚。2.4 数据清洗与落库细节采集原始数据只是开始真正给后续可视化、预测“喂饭”的是清洗环节。我常用的清洗操作包括销量字段电商页面里常写成“已售 5万”或“5000”先转成标准数字再考虑是否需要单位换算。价格字段去掉“”、逗号和“促销价”前缀统一转成 float。规格字段比如“颜色:黑色/尺寸:XL”这种拆成结构化键值对预测时可以作为特征。时间字段统一成YYYY-MM-DD HH:MM:SS别让2024/01/01和2024-01-01混在一个表里。去重逻辑以“商品ID 日期”为唯一键重复采集时按更新时间覆盖。如果数据要喂给机器学习模型清洗后最好再做一次标准化。比如销量列如果有极端值可以用RobustScaler或者做 log1p 变换把拖尾严重的分布拉平后面模型收敛会顺畅很多。3. Flask 后端与可视化系统的构建3.1 Flask 应用组织不写成大杂烩很多同学的 Flask 代码是“一个文件里又写路由又连数据库又算模型”跑起来没问题改起来想哭。我做这个项目时把目录拆成app/ __init__.py # 创建 Flask app、注册蓝图 routes/ __init__.py chart_api.py # 可视化接口 predict_api.py # 预测接口 product_api.py # 商品查询接口 services/ collector.py # Selenium 采集调度 cleaner.py # 数据清洗 predictor.py # 模型加载与预测 models/ database.py # MySQL 连接 config.py # 配置项 run.py # 启动入口用蓝图来挂路由最关键的好处是接口职责一目了然。比如/api/chart/sales_trend返回销量趋势数据/api/predict/future_sales返回未来 N 天预测值前端调接口时也不至于到处找 URL。一个干净的 JSON 接口大概是from flask import Blueprint, jsonify, request chart_api Blueprint(chart_api, __name__) chart_api.route(/api/chart/sales_trend, methods[GET]) def sales_trend(): product_id request.args.get(product_id, typeint) df get_sales_data(product_id) # 伪代码从 MySQL 读取并聚合 return jsonify({ code: 0, data: { dates: df[date].tolist(), sales: df[sales].tolist() } })这里有个容易被忽视的点——接口必须统一返回格式不要一个接口返回{code: 0, data: ...}另一个直接返回裸数组。统一格式后前端处理异常才轻松也方便后期调试。3.2 可视化选型与 ECharts 使用要点可视化图表我首推 ECharts核心原因就一个开箱即用但扩展性也够强。折线图展示销量趋势、柱状图对比各类目成交、饼图看类目占比、热力图看不同时段销量热区一张页面能画得很完整。图表交互还支持缩放、提示框、数据刷选给答辩演示加不少分。前端加载数据的方式我建议走异步接口拉 JSON不要在后端直接拼 HTML 字符串。也就是用 fetch 或 axios 请求 Flask 接口拿到数据后设置 ECharts 的option。ECharts 里一个典型的销量趋势图配置大概是fetch(/api/chart/sales_trend?product_id1001) .then(res res.json()) .then(res { const chart echarts.init(document.getElementById(salesChart)); chart.setOption({ title: { text: 近30天销量趋势 }, tooltip: { trigger: axis }, xAxis: { type: category, data: res.data.dates }, yAxis: { type: value }, series: [{ type: line, data: res.data.sales, smooth: true }] }); });有一点必须提前做图表容器要大至少height: 400px不然图表会占不满页面同时要在window.onresize里调用chart.resize()否则浏览器缩放后图表会变形现场演示时很尴尬。3.3 部署交付本地跑通之后怎么上线毕设系统答辩通常以本地演示为主所以我只建议做一种轻量部署本地用 Flask 自带开发服务器演示如果有线上展示需求再用 gunicorn 加 nginx。这里有个容易出错的地方——Flask 开发服务器默认绑定127.0.0.1:5000别人访问不到最好在启动时指定python run.py --host0.0.0.0 --port5000或者写成if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)依赖管理用requirements.txt固定版本千万别用pip freeze requirements.txt直接导出因为会带出一堆和项目无关的包。推荐手工整理一份只保留 Flask、selenium、pandas、scikit-learn、pymysql 这些关键依赖。服务部署到云服务器的话可以考虑 Docker。但要注意Selenium 在容器里跑需要额外安装 Chrome 和对应驱动还要设置--no-sandbox否则驱动会启动失败。这部分会在问题排查里细说。4. 销量预测机器学习部分怎么落地4.1 数据处理预测的本质是构造特征机器学习里的数据处理一句话说就是把“历史的规律”变成“模型的输入”。对于销量预测原始数据是商品每天卖了多少钱、多少件、什么价格、有没有参加活动但这些不能直接塞进模型必须整理成特征矩阵。我常用的特征包括时间特征星期几、是否周末、是否月初/月末、月份序号。这些能帮助模型捕捉周期性。历史销量特征过去 7 天、14 天、30 天的平均销量或者前一天的销量。滞后期特征对销量预测往往是最重要的。价格特征当天价格、价格环比变化、是否促销。电商场景里“降价 活动”对销量影响非常大。外部特征节假日标记比如双十一、618 这类大促如果数据里有必须单独做一列 0/1 标记。直接用 pandas 构造特征大概是这种风格import pandas as pd def build_features(df): df df.sort_values(date).copy() df[sales_lag1] df[sales].shift(1) df[sales_lag7] df[sales].shift(7) df[sales_roll_mean_7] df[sales].rolling(7).mean() df[weekday] df[date].dt.weekday df[is_weekend] (df[weekday] 5).astype(int) df[price_change] df[price].pct_change() return df.dropna()这里有个非常常见的坑shift和rolling会产生缺失值必须在训练前dropna否则模型会报错或者学到一堆空值。另一个坑是“用未来信息预测过去”——比如把当天的销量作为特征去预测当天销量这在训练集里可能得到超高精度但一上测试就崩这就是典型的数据泄漏。做时序预测时训练集测试集切分不能用train_test_split随机乱切必须按时间顺序切比如用前 80% 的时间段训练后 20% 验证。4.2 算法选型从线性回归到随机森林销量预测这个任务算法不是越复杂越好。我建议先跑一个最简单的线性回归作为 baseline但是因为电商销量普遍有周期性纯线性模型往往预测效果一般。实际我测试下来随机森林在这个数据集上表现最稳因为它能捕捉非线性和特征交互而且对异常值没那么敏感。XGBoost 精度更高但调参成本也大对于毕设来说随机森林足够出彩。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error model RandomForestRegressor(n_estimators300, max_depth10, random_state42) model.fit(X_train, y_train) y_pred model.predict(X_test) mae mean_absolute_error(y_test, y_pred) rmse mean_squared_error(y_test, y_pred, squaredFalse) print(fMAE{mae:.2f}, RMSE{rmse:.2f})如果数据有明显的时间趋势比如销量逐年上涨可以在随机森林之外再对比一个 Prophet它对节假日和周期性的建模更自然。但要记住答辩不是比拼复杂度只要能解释清楚为什么选它、效果怎么验证就已经及格了。4.3 算法优化别只想着换模型先做特征筛选和调参标题里有“Agent 算法优化”这个关键词在实际项目里我把它理解成一种“自动化寻优”的思路——就是不让模型参数全凭手调而是用网格搜索这类方式让程序自己去找最优组合。相当于给模型配了一个自动调参的小助手帮它遍历候选参数组合挑表现最好的那套配置。具体做法很简单。先定义参数候选范围然后交给GridSearchCV跑from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200, 300], max_depth: [5, 10, None], min_samples_split: [2, 5] } gs GridSearchCV(RandomForestRegressor(random_state42), param_grid, cv3, scoringneg_mean_absolute_error) gs.fit(X_train, y_train) print(gs.best_params_)跑网格搜索时要注意时序数据不能直接用默认的交叉验证因为随机切分会打乱时间顺序导致模型看到未来数据。一种妥协做法是手动把训练数据按时间切成几段而不是依赖cvk的随机切分。如果数据集不大也可以用“前 N 天训练后面连续验证”这样的滚动方式效果更贴近真实使用场景。特征筛选也值得做。我跑完随机森林之后会打印feature_importances_把最重要的特征列出来如果某些特征重要性几乎为零可以考虑删掉降低模型复杂度和过拟合风险。4.4 模型落地把训练好的模型接进 Flask模型训练好之后不能只躺在 Jupyter Notebook 里必须接进 Web 系统里给用户用。最常见的做法是用joblib或pickle把模型序列化保存然后在 Flask 启动时加载一次预测接口直接调用import joblib model joblib.load(models/sales_model.pkl) chart_api.route(/api/predict/future_sales, methods[POST]) def future_sales(): data request.get_json() features build_feature_from_request(data) # 前端传商品ID、日期等 prediction model.predict([features])[0] return jsonify({code: 0, data: {predicted_sales: float(prediction)}})这里有个细节模型加载很耗时不要在每次请求里都joblib.load一遍应该在 Flask 应用启动时全局加载一次。另外一个经验是模型文件放在哪里要规划好如果放在models/目录下部署时记得带上这个目录以免服务器上找不到文件直接 500。5. Hadoop 在这个系统里的合理定位5.1 别把 Hadoop 用成摆设Hadoop 是这套毕设里最容易“翻车”的部分。为什么这么说因为在只有几千条商品数据时Hadoop 确实帮不上忙单机 MySQL 处理绰绰有余。但题目里既然给了 Hadoop你不能不用也不能硬造一个不合理场景。我的做法是把它定位成“离线数据加工层”当原始访问日志、历史订单数据积累到一定规模时由 Hadoop 集群定期做离线清洗和统计产出“类目销量报表”“商品热度排行”这类结果再导出给 Flask 做可视化。这个定位在答辩时能立住脚——系统数据量小的时候走轻量处理数据量大时能平滑切换到大数据能力这种设计思路本身就是一个加分点。5.2 伪分布式 vs 集群毕设推荐用什么真实集群需要至少三台服务器做毕设不划算我推荐直接用伪分布式模式一台机器搞定。所谓伪分布式就是所有 Hadoop 守护进程都跑在同一台机器上但配置文件和真实集群结构保持一致这样既能完整展示 HDFS 和 YARN 的工作机制又不需要额外资源。伪分布式搭建的前置条件主要是 JDK 8 和 SSH 免密登录。JDK 8 可以用java -version验证如果系统已经装了 JDK 17需要注意 Hadoop 版本对 Java 版本的兼容性很多旧版 Hadoop 在 JDK 17 上会直接启动失败。搭建步骤大致如下下载 Hadoop 二进制包并解压配置HADOOP_HOME环境变量。修改core-site.xml设置fs.defaultFS为hdfs://localhost:9000。修改hdfs-site.xml设置dfs.replication为 1并指定dfs.namenode.name.dir和dfs.datanode.data.dir。修改yarn-site.xml启动 YARN 用于资源调度。执行hdfs namenode -format格式化 NameNode然后启动服务。其中格式化 NameNode 是最常见的坑——如果你之前已经初始化过文件系统再次格式化会报“NameNode address already in use”之类的问题需要先停掉所有 Hadoop 进程再格式化。另外dfs.replication在伪分布式下必须设为 1因为只有一台 DataNode默认副本数 3 会导致数据一直处于 under-replicated 状态控制台会一直报警。如果你觉得本地装 Hadoop 太重也可以搜一下 Hadoop 的 Docker 镜像用容器方式体验全部流程。Docker 镜像的好处是免去环境配置缺点是数据不持久容器删了就全部丢失所以实验数据要定期导出到宿主机。5.3 与主系统集成让 HDFS 真正被“用起来”只有 Hadoop 单跑起来还不够系统里必须有一条链路能体现它的价值。我做的是这样Selenium 采集到的原始 JSON 日志除了进 MySQL还定期批量上传到 HDFS 的/data/logs目录然后用 Hadoop 自带的能力或者写个简单的 MapReduce 程序统计每个类目的销量 TopN统计结果落回 HDFS 的一个结果目录最后由 Flask 通过 WebHDFS 或者读取导出文件的方式把这些统计结果展示到前端。这种设计逻辑很顺MySQL 管实时业务数据HDFS 管历史日志和离线统计。答辩时讲“系统支持海量商品数据的离线分析”就不再是空话而是有真实的落盘和计算过程支撑的。# 把本地采集日志放到 HDFS hdfs dfs -mkdir -p /data/logs hdfs dfs -put ./logs/product_20250101.json /data/logs/6. 常见问题与排查技巧实录6.1 Selenium 采集与反爬问题速查现象原因解决办法元素找不到页面 JS 未加载完就取节点用 WebDriverWait 显式等待别用固定 sleep翻页后抓不到数据新窗口或 iframe 没有切换用driver.switch_to.window()或switch_to.frame()列表页只加载前 30 个商品懒加载没触发循环滚动到底部观察列表长度是否继续变化频繁触发验证码访问频率过高随机休眠 2~5 秒随机 UA控制采集时间段headless 模式下取不到数据有些站点检测无头浏览器加--disable-blink-featuresAutomationControlled或改用真实窗口演示这里要特别提一句Selenium 在服务器上跑 headless 模式时如果 JS 较多显式等待时间可以适当拉长到 15~20 秒不要设成固定 3 秒。我踩过一次坑本地 5 秒能加载完的页面服务器上因为带宽和性能问题 10 秒都加载不完结果大量元素超时采集成功率掉到一半以下。6.2 Flask 部署与接口联调常见问题最经典的三个问题一是静态文件 404。Flask 的默认静态目录是static/但如果你的前端页面把 ECharts 的 JS 文件放在static/js/之外或者直接引用了外部 CDN 地址离线演示时图表就会白屏。稳妥做法是提前把 echarts.min.js 下载到本地静态目录这样就算答辩现场断网也能正常展示。二是接口中文乱码。Flask 的jsonify默认通过 UTF-8 返回正常情况下没问题但如果你在代码里手动拼了 JSON 字符串再配合旧版本浏览器就可能导致乱码。建议只使用jsonify不要手动json.dumps后直接return。三是CORS 跨域。如果前端页面和 Flask 不是同域部署比如前端在 5500 端口Flask 在 5000 端口浏览器会拦截跨域请求。解决方式是用 Flask-CORS 扩展或者统一部署在同域下。我惯用后者答辩时少一个不可控因素。6.3 Hadoop 与预测结果的常见问题Hadoop 伪分布式最常见的报错是“内存不足”默认的 YARN 配置会为每个容器分配较大内存而 8G 内存的笔记本往往扛不住容器直接被杀。解决办法是在yarn-site.xml里调小资源参数property nameyarn.nodemanager.resource.memory-mb/name value4096/value /property property nameyarn.scheduler.maximum-allocation-mb/name value2048/value /property预测结果异常则有两个高频原因。第一个是训练集和测试集数据分布差异大比如训练数据大多是平销但测试集恰好落在双十一大促期间模型预测值就会偏低很多。这属于数据漂移问题解决思路是特征里加入“是否有大促”标记或者按照同周期的历史数据重新训练。第二个是特征泄漏之前提过的shift使用不当就会造成这个问题排查技巧是看训练集的 MAE 是否异常低如果低到逼近 0基本可以断定泄漏了。写在最后几个让我少走弯路的经验如果让我重做一遍这个题目我会在第一周就把所有模块的数据接口定下来。先把 Flask 的假数据接口跑通前端图表能显示再回头填采集和预测的真实数据。这样即使后面采集卡壳了演示也永远有个能看的东西兜底不至于最后几天才把所有模块连起来手忙脚乱。另一个经验是预测精度不需要追求极致。电商销量预测本身就是高噪声任务真实业务里误差 30% 都算正常。答辩时与其吹精度多高不如说清楚误差是怎么评估的、哪些样本预测效果好、哪些场景效果差。这种对模型边界的思考反而是加分项。最后数据样本的积累越早越好。我从第二周开始就每天定时采一轮商品数据到答辩前攒了两个月的历史数据模型的周期特征才有足够样本去学。如果你最后一周才开始采集那预测部分只能靠造数据答辩一细问就站不住脚。Hemmed up with数据都攒好了,这个系统的每一层就都有了真实支撑无论评分老师问架构还是问业务你都能拿出东西来讲。