尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

电商数据采集分析与销量预测系统:Flask+Selenium+机器学习完整实战

电商数据采集分析与销量预测系统:Flask+Selenium+机器学习完整实战 做电商类毕业设计的同学应该都见过类似题目“python电商数据采集分析与销量预测系统”。这种题目站在老师视角很有吸引力一个系统把爬虫、Web开发、数据分析、机器学习全串起来了工作量看起来足技术含量也够。但真到自己上手很多人会卡在一个地方不知道这些模块怎么搭在一起采集完的数据往哪放模型训练完又怎么展示。我今年帮朋友完整跑通过这套系统用 Flask 做后端、Selenium 采动态页面、机器学习做销量预测整个过程踩了不少坑也总结出一些可以直接照搬的经验。这篇文章就把我从零到一实现的完整路子讲清楚给正在做毕业设计或者想快速搭一套类似系统的你做一个参考。1. 项目整体思路与功能拆解1.1 选型之前先想清楚这个系统到底要做什么电商数据采集分析与销量预测系统拆开看其实是三个独立问题数据从哪来、数据怎么展示、怎么用数据预测未来销量。不少同学一上来就埋头写代码结果采集模块和数据可视化各自为战模型是模型、页面是页面最后演示的时候只能一个功能一个功能单独讲整个系统显得很碎。我先定了一个完整数据流Selenium 从电商平台采集商品信息清洗后存进 SQLiteFlask 读取数据库一方面提供统计接口给可视化页面另一方面把特征数据交给机器学习模型模型训练完成后再封装成预测接口用户在页面上输入商品特征就能得到销量预测结果。这条链路闭环之后每一个模块都有明确输入和输出答辩时也容易讲清逻辑。功能模块我分成五块数据采集、数据清洗、数据存储、数据可视化、销量预测。听起来多但每一块都有现成工具支撑。真正的难点不在某个单独功能而是把五块串起来时各种接口、格式、调用方式如何对齐。1.2 技术选型背后的现实原因题目里明确写了 Flask、Selenium、机器学习这些词但为什么偏偏是这几个组合很多人未必想过。先说 Flask。电商数据系统通常需要快速开发、方便演示Flask 比 Django 轻不少一个 main.py 就能把服务跑起来模板语法也简单适合毕设这种规模。而且 Flask 的请求上下文和路由机制很容易理解哪怕你之前只写过基础 Python学起来压力也不大。Selenium 是这里不可缺少的一环。现在主流电商页面基本都是 JavaScript 动态渲染直接用 requests 只能拿到加载前的空壳拿不到商品价格、评论数这些关键数据。Selenium 相当于开了一个真实浏览器页面上能看到什么程序就能采集到什么处理动态加载的页面非常直接。机器学习部分我用了 scikit-learn 做回归模型同时加了一个用 TensorFlow/Keras 搭的 LSTM 作为对比实验。这里有一个很多人的误区毕业设计题目里写“深度学习”不等于必须把深度学习作为唯一方案更合理的做法是传统机器学习模型和深度学习模型都做一遍比较效果然后分析原因。这样既显得工作量足又规避了数据量太小导致深度学习效果不佳的尴尬。1.3 整体架构和数据流设计系统架构我分成了四层数据采集层、数据存储层、业务逻辑层、展示层。采集层只负责抓取和解析页面不关心数据库结构存储层用 SQLite 起步后续需要可以切换到 MySQL业务逻辑层包含 Flask 路由、数据统计逻辑和模型预测逻辑展示层是 HTML ECharts 页面通过 AJAX 请求后端接口拿数据。这个分层最大的好处是排查问题方便。比如模型预测结果明显不对时不需要翻前端代码直接检查训练数据和特征处理部分就行。如果采集阶段出了问题也只会影响数据库里的数据不会把后端页面一起带崩。2. 数据采集层Selenium 实战要点2.1 Selenium 环境准备与浏览器配置采集模块是整个系统的源头如果数据质量不行后面分析、预测全都白搭。我用的环境是 Python 3.9 Selenium 4.x Chrome 浏览器。Selenium 4 和 3 在用法上有些小变化比如定位元素时推荐使用 find_element不再推荐 find_element_by_id 这种写法写代码的时候要留意。启动浏览器时不要直接裸用默认配置否则访问稍微复杂一点的页面很容易被识别成自动化程序。我常用的配置如下from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager options Options() options.add_argument(--headless) options.add_argument(--disable-gpu) options.add_argument(--no-sandbox) options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) driver webdriver.Chrome( serviceService(ChromeDriverManager().install()), optionsoptions ) executor_url driver.command_executor._url session_id driver.session_id这里有几个细节值得说。headless 模式适合后台跑任务但如果要调试页面结构建议先把 headless 关掉看真实浏览器里到底呈现了什么。AutomationControlled 这个参数是为了去掉浏览器上“正在受自动化控制”的提示条能降低被主动拦截的概率。还要注意驱动版本要和本机 Chrome 版本匹配。我以前手动下载 chromedriver 时经常因为版本不一致报错后来改用 webdriver_manager 自动匹配省了不少事。2.2 页面滚动加载与翻页采集电商网站的商品列表页几乎都是懒加载往下滚动才会加载新商品。只抓页面静态内容顶多拿到前面十几条。处理滚动加载的标准方式是模拟鼠标滚轮每滚一次等图片和商品块渲染完成再继续import time from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def scroll_and_collect(driver, max_scrolls10): goods [] for _ in range(max_scrolls): driver.execute_script(window.scrollTo(0, document.body.scrollHeight)) time.sleep(3) cards driver.find_elements(By.CSS_SELECTOR, .item-wrapper) goods.extend([card.text for card in cards]) # 尝试点击下一页 try: next_btn driver.find_element(By.CSS_SELECTOR, .next-page) if next_btn.is_enabled(): next_btn.click() time.sleep(3) else: break except Exception: break return goods这里要特别注意等待方式。我最初用的是 time.sleep写起来简单但不稳定页面前几秒没加载出来后面的代码马上就会报“找不到元素”。后来改成显式等待WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, .item-wrapper)) )显式等待会等到元素真正出现在DOM里才继续比固定 sleep 高效得多。另外滚动次数和翻页数量要设置上限电商平台的页数往往有几十上百页全量抓取对毕设来说不仅耗时还容易给目标网站造成压力道德和合规层面都不推荐。2.3 字段设计与数据清洗入库采集完的原始数据不能直接用需要先抽取出结构化字段。我最终落地到数据库的表结构设计如下字段名类型说明idINTEGER 主键自增主键titleTEXT商品标题shop_nameTEXT店铺名称priceREAL当前价格original_priceREAL原始价格comment_countINTEGER评论数scoreREAL评分salesINTEGER销量页面展示值categoryTEXT商品类目crawl_timeDATETIME采集时间清洗逻辑主要有四步去重、缺失处理、类型转换、异常值过滤。同一商品在多次采集时可能出现重复记录用 title 加 shop_name 做联合唯一判断。价格字段在页面里可能是“129.00”这种格式需要正则提取数字再转 float。缺失评论数和评分的商品我选择直接删除因为后面的销量预测特征里这两项占比很大硬填默认值会污染模型。合规方面我建议只采集页面上公开可见的信息请求间隔控制在3到5秒不做高频请求不尝试绕过登录验证码目标范围限定在一两个类目。真正到演示阶段数据量有三五百条就足够支撑可视化和模型训练了。3. 数据分析与可视化实现3.1 从数据里提炼出有说服力的指标采集表建好之后接下来要让数据会说话。我做的第一件事是统计整体概况商品总数、平均价格、平均评分、评论数分布区间。这些指标作为可视化页面的顶部概览卡片一眼就能看到系统的数据分析能力。真正有分析深度的是交叉聚合结果。比如用 pandas 按类目分组计算平均销量和平均价格可以看不同品类的销售表现再按价格区间分桶统计销量均值能看出价格和销量的关系。我还计算了评论数和销量的相关系数用这个数字来支撑“评论对销售有正向影响”这种业务结论。这部分是答辩时的高频提问点建议提前准备好两三条能从数据中读出来的结论。import pandas as pd df pd.read_sql_query(SELECT * FROM goods, engine) price_bins pd.cut(df[price], bins[0, 50, 100, 200, 500, 99999], labels[0-50, 50-100, 100-200, 200-500, 500]) grouped df.groupby(price_bins, observedTrue)[sales].mean().reset_index()需要提醒的是电商页面展示的销量经常是累计值而不是某个时间窗口的增量数据所以直接拿它当回归目标会有一定偏差。我的做法是补充采集同商品在多个时间点的销量用后一时间点减前一时间点来构造近似月度销量。就算只采集了两轮也能让模型训练有一个相对合理的目标变量。3.2 Flask 接口如何给前端图表提供数据可视化页面拿数据不是直接查库而是先通过 Flask 定义好 JSON 接口。这样前端和后端解耦后续换图表库或者调整统计口径都不需要大改。一个典型接口写法如下from flask import Flask, jsonify, render_template import pandas as pd from sqlalchemy import create_engine app Flask(__name__) engine create_engine(sqlite:///goods.db) app.route(/) def index(): return render_template(index.html) app.route(/api/category_sales) def category_sales(): df pd.read_sql_query(SELECT category, AVG(sales) AS avg_sales, COUNT(*) AS cnt FROM goods GROUP BY category, engine) data { categories: df[category].tolist(), avg_sales: df[avg_sales].round(2).tolist(), counts: df[cnt].tolist() } return jsonify(data) if __name__ __main__: app.run(debugTrue, port5000)JSON 字段名我用的是小驼峰风格前端读取时直观。接口返回值最好统一成 code、data、msg 的结构方便前端统一处理错误。我最初偷懒直接返回数组后来前端对接时遇到空数据没法区分是正常还是异常才补上统一结构。3.3 基于 ECharts 的看板页面前端部分我用 ECharts 原生 JavaScript 实现没有引入 Vue 或者 React原因是毕设系统不需要重前端框架反而会增加依赖和学习成本。HTML 页面里用几个 div 放图表JavaScript 里用 fetch 请求接口再 setOption 渲染。一个常见问题是图表初始化和异步请求的时序。如果 fetch 还没回来就调 setOption页面会出现空白。我在代码里用了 async/await 保证数据到位后再渲染async function loadCategoryChart() { const res await fetch(/api/category_sales); const data await res.json(); const chart echarts.init(document.getElementById(categoryChart)); chart.setOption({ xAxis: { type: category, data: data.categories }, yAxis: { type: value }, series: [{ type: bar, data: data.avg_sales }] }); } window.addEventListener(resize, () { const chart echarts.init(document.getElementById(categoryChart)); chart.resize(); });页面布局不用太花哨上下结构就行顶部放 KPI 卡片中间放价格-销量散点图和类目销量柱状图下方放评论数趋势或商品排行。做可视化的时候要记住一个原则图表是为了说明问题不是为了堆技术名词。老师问到每张图时你要能说清楚图表背后反映了什么业务现象。4. 销量预测模型机器学习和深度学习的取舍4.1 特征工程是本系统的重中之重销量预测模型效果好不好特征工程的作用往往比算法选择更大。这一点我在实际跑数据时体会特别深。我最终使用的特征是价格、原始价格、评论数、评分、商品上架天数、是否参加促销、类目编码。其中上架天数需要拿采集时间减去商品详情页展示的上架时间这步比较麻烦因为不同平台展示上架时间的方式不一样有的显示具体日期有的只显示“30天售出 xxx”实际处理时我会做一个减法近似。数值型特征直接进模型前要做标准化。评论数和价格的数量级差别很大如果不做处理梯度下降类模型会很难收敛树模型虽然不受影响但后续想比较多个模型时统一标准化更方便。用 scikit-learn 的 StandardScaler 就可以from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split features [price, comment_count, score, days_on_shelf, promotion, category_code] X df[features] y df[monthly_sales] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)这里有一个很容易犯的错误先对全部数据 fit 再切分训练测试集或者对测试集单独 fit都属于数据泄露。正确做法是只在训练集上 fit测试集只做 transform。4.2 先跑传统机器学习模型打底我先跑了线性回归和随机森林回归。线性回归作为基准模型虽然预测精度一般但胜在可解释性很强回归系数能直接告诉你价格每变化一个单位销量大致变化多少。随机森林则能处理特征之间的非线性关系实际效果通常比线性回归好一截。评估指标我用了三个平均绝对误差 MAE、均方根误差 RMSE、决定系数 R2。MAE 最直观意思是预测销量和真实销量平均差了多少R2 则反映模型对数据波动的解释程度毕业设计里建议把 R2 作为主要展示指标同时在论文里解释为什么 R2 不接近 1 也不是失败。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np model RandomForestRegressor(n_estimators300, max_depth12, random_state42) model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) mae mean_absolute_error(y_test, y_pred) rmse np.sqrt(mean_squared_error(y_test, y_pred)) r2 r2_score(y_test, y_pred)调参时我经验是优先调 max_depth 和 n_estimatorsmax_depth 太深容易过拟合在毕设这种几百条数据规模下12 到 15 层足够。n_estimators 不是越大越好300 棵和 800 棵差距很小但训练时间明显增加。4.3 深度学习 LSTM 作为对比实验题目里提到了深度学习因此我在传统模型之外加了一个 LSTM 做时序预测对比。LSTM 更适合把销量按时间排列预测下一个时间点的销量而不是像随机森林那样直接输入商品静态特征、输出销量。构造 LSTM 输入时需要把样本转成滑窗格式也就是用过去 N 天的销量预测第 N1 天。窗口长度我取了 7代表用一周的销量趋势预测下一天。归一化仍然重要而且要注意预测完要 inverse_transform 才能和原始销量对比。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense def build_lstm(input_shape): model Sequential() model.add(LSTM(64, activationrelu, input_shapeinput_shape)) model.add(Dense(1)) model.compile(optimizeradam, lossmse) return model # 假设 X_ts 形状为 (样本数, 7, 特征数) model_lstm build_lstm((X_ts.shape[1], X_ts.shape[2])) model_lstm.fit(X_train_ts, y_train_ts, epochs50, batch_size16, validation_split0.1, verbose0)这个模型用起来有一个极其现实的坑数据量太少时 LSTM 效果大概率不如随机森林。我当时只有两百多条时间序列样本LSTM 的 R2 不到 0.2随机森林能跑到 0.45 左右。但这并不代表深度学习这条路走错了。在论文里这个现象本身就值得分析深度学习需要大量数据支撑在数据量受限的电商场景中传统机器学习反而更可靠。这样的结论比“我用了深度学习所以系统很先进”更有说服力答辩老师也更认可。4.4 模型保存与 Flask 集成训练完成后不要每次启动 Flask 都重新训练。用 joblib 保存模型启动时判断有没有模型文件有就加载没有才训练import joblib joblib.dump(model_rf, models/random_forest.pkl) joblib.dump(scaler, models/scaler.pkl) # Flask 启动时加载 rf_model joblib.load(models/random_forest.pkl) scaler_model joblib.load(models/scaler.pkl)在 Flask 里预测的接口设计成 POST接收 JSON 格式的特征值返回预测销量。注意 Flast 里的全局变量在使用时会不会在多线程环境下有问题不过毕设演示单用户访问基本没有并发压力不用过度设计。5. 前后端联调与系统部署5.1 路由设计与预测接口规范系统路由我划分得很清楚路由方法功能/GET首页渲染可视化看板/api/overviewGET返回 KPI 概览数据/api/category_salesGET返回类目销量数据/api/price_salesGET返回价格-销量散点数据/api/predictPOST接收特征JSON返回预测销量/api/trendGET返回销量趋势数据预测接口是系统里最重要的部分输入输出格式一定要设计好。我在前端页面做了一个表单用户填价格、评论数、评分、上架天数然后提交给 /api/predict。接口内部先做特征编码再标准化最后调用模型返回结果app.route(/api/predict, methods[POST]) def predict(): data request.get_json() features [ float(data[price]), float(data[comment_count]), float(data[score]), float(data[days_on_shelf]), int(data[promotion]), int(data[category_code]) ] features_scaled scaler_model.transform([features]) pred rf_model.predict(features_scaled)[0] return jsonify({code: 0, data: {predicted_sales: round(float(pred), 2)}})这里类别编码需要保持训练时的一致。如果训练集里 category_code 是 0、1、2 这种整数映射预测接口里也要用同一张映射表我直接保存在一个 JSON 配置文件里避免前后端各写一套。5.2 部署演示时最容易踩的三个坑第一个坑是 Flask 默认的 debug 模式。debugTrue 虽然改代码会自动重启但演示时一旦报错会出现一个带交互调试器的页面看起来很不专业。正式演示前一定改成 debugFalse。第二个坑是 Selenium 的浏览器进程。很多同学在采集完数据后忘记 driver.quit()程序结束但 Chrome 进程还留在后台搞几次之后系统资源被占满页面加载变慢。采集代码里要加 try/finally 确保退出或者写个清理脚本批量杀掉残留的 Chrome 进程。第三个坑是模型加载时机。如果在 Flask 文件顶部加载模型服务启动期间模型文件损坏或者不存在整个服务直接崩掉。更加稳妥的做法是封装一个 get_model 函数第一次调用预测接口时才加载模型配合缓存避免重复加载。部署上线如果只是给老师演示本地跑 Flask 完全够用。如果想把系统部署到服务器建议用 gunicorn 代替 python app.py静态文件用 Nginx 托管但这些都是加分项不影响核心功能的完整度。6. 常见问题与避坑清单6.1 采集阶段常遇到的问题速查现象主要原因解决办法WebDriverException: unknown errorChrome 和 chromedriver 版本不匹配用 webdriver_manager 自动匹配版本TimeoutException页面元素加载过慢把等待时间调长或检查是否被限流NoSuchElementException页面结构变化或懒加载未触发先手动滚动页面再用显式等待采集到内容为空白headless 模式渲染异常先关闭 headless 调试再重新开启请求频率过高被临时屏蔽采集间隔太短设置 3 到 5 秒随机延时降低频次这里要专门说一句遇到临时屏蔽时不要费尽心思去找各种绕过手段对毕业设计来说没有意义。最理性的做法是降低速度或者换一个公开接口数据源。合规地完成所有模块才是这个题目的正解。6.2 模型训练中的隐藏问题我第一版预测模型跑出来 R2 是负数检查后发现是特征里有未来信息。比如我把“上架天数”和“销量”都放进特征里表面上看模型很准实际上销量数据里已经包含了时间趋势和上架天数高度共线模型学到的是时间带来的自然增长而不是商品属性对销量的影响。这种数据泄露是最隐蔽、最能被老师问倒的问题。另一个常见问题是类别特征直接编码成整数后比如类目 1、2、3、4模型会认为类目之间存在大小关系实际上它们是无序的。正确做法是用 one-hot 编码但增加维度后样本量少时容易稀疏所以我最后只保留了 top 类目编码其他合并成“其他”。评价指标不要只看 R2。对于销量波动大的商品RMSE 会特别大因为个别极端值会主导误差。我通常同时报告 MAE 和 RMSE并在答辩稿里说明两个指标各自的业务含义。6.3 整体开发节奏建议把系统完整跑一遍我建议按这个顺序推进先写采集脚本保证能稳定拿到几百条干净数据然后用 pandas 做分析和可视化快速验证数据有没有价值再训练基础模型先把接口串通最后才优化模型效果和页面细节。这样每一步都有阶段性成果不会等到最后关头才发现数据有问题模型全部白做。如果时间充足可以在模型部分增加一个超参数网格搜索或者用 SHAP 做特征重要性分析。这些点不需要复杂代码但是说明你对模型有更深理解在论文里是非常好的加分素材。我个人做完整套系统后的体会是这个题目真正考察的不是某一个算法有多先进而是你能不能把采集、存储、分析、建模、展示这条完整链路打通。很多人卡在中间不是因为代码写不出来而是没有站在整个系统的高度去思考模块之间的接口和数据流。做毕设不是比拼技术名词哪怕你把随机森林调得非常好、把页面做得足够清晰也比堆一堆没跑通的深度学习代码强得多。最后再分享一个小技巧所有模块都用日志把进度打印出来演示的时候哪怕出了小问题你也能快速判断是采集问题、数据库问题还是模型接口问题这个习惯能让你省掉大量调试时间。
返回列表