ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python动态爬虫+LSTM时序预测+SHAP可解释性+Dash可视化全链路实践

Python动态爬虫+LSTM时序预测+SHAP可解释性+Dash可视化全链路实践 简介本资源是一套面向高校计算机及相关专业学生的爬虫与数据分析综合实践项目覆盖信息采集、时序预测与机器学习分析全流程适用于毕设、课设、竞赛及进阶学习。项目包含完整可运行源码、详细设计文档、可视化分析报告及配套数据支持零基础入门与二次开发。压缩包共472个文件主体为105个SDI模型中间状态、301个PKL预训练模型与特征缓存、13个Python主程序及8个IPYNB交互式分析脚本辅以CSV数据集、YML配置、Dockerfile容器化部署文件等整体174.05MB结构清晰、模块解耦。已有63人下载学习资源内含LSTM股价/舆情预测实现、Scrapy分布式爬虫工程、Matplotlib/Plotly动态可视化图表生成逻辑以及MySQL日志与Binlog解析模块便于理解数据采集—清洗—建模—可视化的全链路实践细节。1. 这不是“爬虫LSTM可视化”的拼盘而是一条闭环数据价值链从网页动态加载页抓取结构化数据到用LSTM建模时序依赖关系再到用机器学习解释变量贡献度最后生成可交互的分析报告——整套流程在单机Python环境下可复现源码已按模块解耦支持替换为真实业务字段如电商销量、IoT传感器读数、金融行情直接投产很多初学者看到标题就去搜“LSTM预测股票”结果卡在第一步数据根本拿不到。真实场景中目标网站往往用JavaScript渲染、带反爬头、分页异步加载requests直接GET返回空HTML拿到数据后又陷入“模型跑通但结果不准”的困局——因为没做特征工程清洗异常值、没对时序数据做滑动窗口对齐、没用机器学习模型解释LSTM输出的黑箱预测最后可视化只是plt.plot一张折线图无法支撑业务决策。本实践项目完整覆盖这四个断点用SeleniumRequests混合策略稳定获取动态内容用sktime统一接口封装LSTM时序建模流程用SHAP值量化每个特征对预测结果的影响强度最终用Plotly Dash构建带筛选控件和响应式布局的本地报告页面。适合有Python基础、想把爬虫、时序建模、可解释AI和前端展示串成一条线的工程师。2. 稳定获取动态渲染数据Selenium驱动Requests会话复用反爬头精细化配置2.1 为什么不用纯Requests动态渲染页的DOM结构必须由浏览器引擎解析目标网站如某天气预报平台、某行业资讯站采用Vue/React框架关键数据藏在XHR请求返回的JSON中或通过window.__INITIAL_STATE__注入。纯Requests无法执行JS导致response.text里只有div idapp/div。此时必须引入无头浏览器驱动。但Selenium全程模拟浏览器开销大、易被检测因此采用“Selenium只负责登录和触发AJAXRequests接管后续高频请求”的混合模式——既绕过JS渲染障碍又避免频繁启停浏览器。提示不要用PhantomJS已废弃ChromeDriver需匹配本地Chrome版本若部署在Linux服务器必须加--headlessnew和--no-sandbox参数否则启动失败。2.2 构建可复用的会话对象Cookie继承与User-Agent轮换from selenium import webdriver from selenium.webdriver.chrome.options import Options import requests def init_driver(): chrome_options Options() chrome_options.add_argument(--headlessnew) chrome_options.add_argument(--no-sandbox) chrome_options.add_argument(--disable-dev-shm-usage) chrome_options.add_argument(--disable-blink-featuresAutomationControlled) # 隐藏webdriver特征 chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) driver webdriver.Chrome(optionschrome_options) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}); }) return driver def get_session_with_cookies(driver, base_url): 用Selenium访问首页获取初始Cookie注入Requests会话 driver.get(base_url) # 等待页面加载完成此处用显式等待更健壮 driver.implicitly_wait(5) # 提取当前会话Cookie selenium_cookies driver.get_cookies() session requests.Session() # 将Selenium Cookie转为Requests格式 for cookie in selenium_cookies: session.cookies.set(cookie[name], cookie[value]) # 设置通用Headers session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: base_url, X-Requested-With: XMLHttpRequest }) return session # 使用示例 driver init_driver() session get_session_with_cookies(driver, https://example-weather.com/) # 后续所有API请求都用这个session无需重复登录这段代码的核心逻辑是Selenium仅作为“Cookie生成器”运行一次之后Requests会话复用其认证状态。session.headers.update()中预置了高仿真User-Agent避免因Header缺失被拦截Referer和X-Requested-With确保请求符合AJAX规范。实际项目中若目标站有IP频率限制可在session.headers中加入随机User-Agent列表并轮换但不推荐直接使用代理IP池——多数免费代理延迟高、稳定性差反而增加超时错误更可靠的做法是控制请求间隔time.sleep(random.uniform(1,3))并配合重试机制。2.3 分页与增量抓取用URL参数构造时间戳过滤避免重复动态网站分页常通过URL参数如?page2size20或滚动加载。本项目采用参数构造法因其可控性强。关键点在于识别分页规律观察Network面板中XHR请求的URL变化提取可变参数名如offset、cursor设置终止条件不依赖“下一页按钮是否存在”而是检查API返回数据长度——当某次请求返回空列表或少于预期条数如len(data) 20即判定为末页增量更新在数据库或CSV中记录最后抓取的update_time字段下次启动时添加start_time2024-01-01参数避免全量重爬。import time import random from urllib.parse import urljoin def fetch_paginated_data(session, base_api_url, max_pages100): all_data [] for page in range(1, max_pages 1): params { page: page, size: 50, sort: update_time:desc } try: response session.get(base_api_url, paramsparams, timeout10) response.raise_for_status() data response.json() # 终止条件返回空或数据量不足 if not data or len(data) 50: print(f第{page}页数据不足50条停止抓取) break all_data.extend(data) print(f已抓取第{page}页累计{len(all_data)}条) # 随机延时防封 time.sleep(random.uniform(1.5, 3.0)) except requests.exceptions.RequestException as e: print(f第{page}页请求失败: {e}) break return all_data # 调用示例 weather_api https://api.example-weather.com/v1/forecast raw_data fetch_paginated_data(session, weather_api)timeout10防止请求挂起response.raise_for_status()自动抛出HTTP错误如403random.uniform(1.5, 3.0)比固定sleep更接近人类行为。此函数返回原始JSON列表后续交由Pandas清洗——这才是工程化爬虫的正确分工Selenium/Requests只管“拿得到”Pandas只管“理得清”。3. LSTM时序建模全流程从滑动窗口构造到多步预测与误差校准3.1 为什么选LSTM而非ARIMA处理非线性趋势与长周期依赖的不可替代性传统统计模型如ARIMA假设时间序列平稳且服从线性关系但真实业务数据如电商日销量、服务器CPU使用率常含突发峰值、节假日效应、缓慢漂移等非线性特征。LSTM通过门控机制输入门、遗忘门、输出门选择性记忆长期依赖能捕捉“上周同一时段销量激增”这类跨周期模式。本项目用sktime库封装LSTM因其提供统一接口ForecastingPipeline可无缝接入特征工程、模型训练、预测评估全流程避免手写Keras循环的繁琐。注意LSTM不是万能药。若序列长度50点、采样间隔1小时、存在强季节性如月度报表应优先尝试Prophet或N-BEATS本实践聚焦日粒度、300点、含噪声的连续型指标如温度、股价、流量。3.2 滑动窗口标准化用MinMaxScaler保障梯度稳定用to_numpy()规避DataFrame索引陷阱LSTM输入必须是三维张量(samples, timesteps, features)。常见错误是直接用df.values导致索引错位或未归一化引发梯度爆炸。本项目采用sktime推荐的SlidingWindowTransformer构造窗口并用MinMaxScaler逐列缩放import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler from sktime.transformations.series import SlidingWindowTransformer from sktime.forecasting.model_selection import temporal_train_test_split # 假设raw_data已转为DataFrame含date和value列 df pd.DataFrame(raw_data) df[date] pd.to_datetime(df[date]) df df.sort_values(date).set_index(date) # 仅对目标列归一化避免污染时间特征 scaler MinMaxScaler(feature_range(0, 1)) df_scaled df.copy() df_scaled[value] scaler.fit_transform(df[[value]]) # 构造滑动窗口用过去7天预测未来1天 window_transformer SlidingWindowTransformer( window_length7, # 过去7个时间点 step_length1, # 每次滑动1步 target_colvalue ) X_windowed, y_windowed window_transformer.fit_transform(Xdf_scaled, ydf_scaled[value]) # X_windowed形状为(样本数, 7, 1)y_windowed为(样本数,) # 转为numpy数组供Keras使用 X_train, X_test, y_train, y_test temporal_train_test_split( X_windowed, y_windowed, test_size0.2 ) # 确保维度正确sktime输出为3D但Keras要求明确batch维度 X_train_np X_train.to_numpy().reshape(-1, 7, 1) X_test_np X_test.to_numpy().reshape(-1, 7, 1) y_train_np y_train.to_numpy() y_test_np y_test.to_numpy()关键点说明SlidingWindowTransformer自动处理时间序列切片比手动for i in range(len(df)-7)更安全MinMaxScaler只作用于value列保留原始日期索引用于后续对齐to_numpy().reshape()强制转换为Keras所需格式-1让NumPy自动推断批量大小temporal_train_test_split按时间顺序分割避免未来数据泄露到训练集。3.3 构建可解释的LSTM模型用Dropout抑制过拟合用EarlyStopping避免冗余训练from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping def build_lstm_model(input_shape): model Sequential([ LSTM(50, return_sequencesTrue, input_shapeinput_shape), # 第一层LSTM返回全部时间步 Dropout(0.2), # 防止过拟合 LSTM(50, return_sequencesFalse), # 第二层LSTM只返回最后时间步 Dropout(0.2), Dense(25), # 全连接层降维 Dense(1) # 输出单点预测值 ]) model.compile(optimizeradam, lossmean_squared_error) return model # 训练模型 model build_lstm_model((7, 1)) early_stopping EarlyStopping( monitorval_loss, patience10, # 连续10轮val_loss不下降则停止 restore_best_weightsTrue # 恢复最优权重 ) history model.fit( X_train_np, y_train_np, batch_size32, epochs100, validation_data(X_test_np, y_test_np), callbacks[early_stopping], verbose1 ) # 预测并反向缩放 predictions model.predict(X_test_np) predictions_actual scaler.inverse_transform(predictions) y_test_actual scaler.inverse_transform(y_test_np.reshape(-1, 1))参数说明LSTM(50)表示隐藏单元数50是经验平衡值太少学不到模式太多易过拟合return_sequencesTrue让第一层输出全部时间步供第二层LSTM接收Dropout(0.2)在每次训练时随机关闭20%神经元强制网络学习鲁棒特征patience10防止模型在验证集上震荡时盲目继续训练restore_best_weightsTrue确保最终模型是验证损失最低时的状态。4. 机器学习解释性分析用SHAP值量化特征贡献定位LSTM预测的关键驱动因子4.1 为什么需要SHAPLSTM的“黑箱”特性阻碍业务信任与归因LSTM预测准确率高但业务方常质疑“为什么今天预测值突然升高是天气影响还是促销活动”单纯看模型权重无法回答——LSTM的权重矩阵是时序卷积结果没有单点特征对应关系。SHAPShapley Additive Explanations基于博弈论计算每个特征对单次预测的边际贡献生成直观的力导向图force plot和摘要图summary plot让“温度上升2℃导致预测值增加15%”这类结论可验证。4.2 构建SHAP解释器用KernelExplainer适配LSTM输出规避深度学习专用解释器的兼容问题TensorFlow/Keras模型需用DeepExplainer但其对自定义层支持不稳定。本项目采用更通用的KernelExplainer将LSTM预测函数包装为黑盒接口import shap import numpy as np # 定义预测函数接收原始特征非窗口化返回标量预测值 def lstm_predict_wrapper(X_raw): X_raw: shape (n_samples, n_features)如[[25.1, 60, 1], [26.3, 55, 0]] 返回: (n_samples,) 的预测数组 # 步骤1对每行样本构造滑动窗口复用前面的window_transformer逻辑 X_windowed_list [] for i in range(len(X_raw)): # 取前7天数据此处简化实际需维护历史缓冲区 # 真实项目中应保存最近7天特征每次append新行再pop最老行 pass # 步骤2归一化 reshape X_scaled scaler.transform(X_raw) # 注意此处需用fit时的scaler X_reshaped X_scaled.reshape(-1, 7, 1) # 步骤3模型预测 preds model.predict(X_reshaped) return preds.flatten() # 使用KernelExplainer计算慢但兼容性强 explainer shap.KernelExplainer(lstm_predict_wrapper, X_train_sample) shap_values explainer.shap_values(X_test_sample[:100]) # 解释前100个样本 # 绘制摘要图纵轴为特征横轴为SHAP值颜色代表特征值高低 shap.summary_plot(shap_values, X_test_sample[:100], feature_names[temp, humidity, is_holiday])提示KernelExplainer需传入背景数据集X_train_sample其大小影响计算精度——建议取训练集的100~200个样本shap_values是二维数组shap.summary_plot自动聚合统计。若追求速度可用TreeExplainer需将LSTM蒸馏为树模型但本实践优先保证解释准确性。4.3 特征工程增强引入滞后特征与移动统计量提升SHAP可解释性原始LSTM仅用value列SHAP解释结果单调。加入业务相关特征如temp_lag1、humidity_rolling_mean_7d后SHAP能揭示“湿度7日均值每升高1%预测值下降0.8%”这类因果线索# 在数据清洗阶段添加特征工程 df_features df.copy() df_features[temp_lag1] df_features[temp].shift(1) df_features[humidity_rolling_mean_7d] df_features[humidity].rolling(window7).mean() df_features[is_weekend] (df_features.index.dayofweek 5).astype(int) # 删除含NaN的行滞后特征导致前几行为空 df_features df_features.dropna() # 重新归一化所有特征列 feature_columns [temp, temp_lag1, humidity_rolling_mean_7d, is_weekend] scaler_features MinMaxScaler() df_features[feature_columns] scaler_features.fit_transform(df_features[feature_columns])此步骤使SHAP分析从“单一指标驱动”升级为“多因子协同解释”直接支撑业务决策若is_weekend的SHAP值显著为正运营团队可提前加大周末资源投放。5. 可视化报告系统用Plotly Dash构建带筛选控件的交互式分析仪表盘5.1 为什么不用Jupyter Notebook静态报告无法满足业务方实时钻取需求Jupyter导出HTML后图表不可交互、筛选器失效、移动端显示错乱。Dash是Python原生框架将Plotly图表、Bootstrap组件、回调函数集成生成独立Web应用。用户点击“选择城市”下拉框图表实时刷新拖动时间范围滑块预测曲线动态重绘——所有操作在本地浏览器完成无需服务器部署。5.2 核心Dash布局用dbc.Container实现响应式栅格用dcc.Graph嵌入Plotly对象import dash from dash import dcc, html, callback, Input, Output, State import dash_bootstrap_components as dbc import plotly.graph_objects as go app dash.Dash(__name__, external_stylesheets[dbc.themes.BOOTSTRAP]) # 布局顶部标题 左侧筛选区 右侧图表区 app.layout dbc.Container([ dbc.Row([ dbc.Col(html.H1(气象预测分析仪表盘, classNametext-center my-4), width12) ]), dbc.Row([ # 左侧筛选控件 dbc.Col([ html.H5(筛选条件), html.Br(), dbc.Label(城市选择), dcc.Dropdown( idcity-dropdown, options[{label: city, value: city} for city in [北京, 上海, 广州]], value北京 ), html.Br(), dbc.Label(预测天数), dcc.Slider( iddays-slider, min1, max7, step1, value3, marks{i: f{i}天 for i in range(1, 8)} ) ], width3), # 右侧主图表 dbc.Col([ dcc.Graph(idprediction-graph), html.Div(idshap-explanation, classNamemt-4) ], width9) ]) ], fluidTrue) # 回调函数用户操作触发图表更新 callback( Output(prediction-graph, figure), Input(city-dropdown, value), Input(days-slider, value) ) def update_graph(selected_city, days): # 此处调用LSTM预测函数返回预测值和真实值 # 为简洁省略具体预测逻辑返回模拟数据 dates pd.date_range(start2024-01-01, periodsdays7, freqD) actual np.random.normal(15, 5, days7) np.sin(np.arange(days7)/10)*3 predicted actual[:-days] np.random.normal(0, 1, days7-days) fig go.Figure() fig.add_trace(go.Scatter(xdates[:-days], yactual[:-days], name实际值, modelinesmarkers)) fig.add_trace(go.Scatter(xdates[-days:], ypredicted, name预测值, modelinesmarkers, linedict(dashdash))) fig.update_layout(titlef{selected_city}未来{days}天温度预测, xaxis_title日期, yaxis_title温度(℃)) return fig # 启动服务 if __name__ __main__: app.run_server(debugTrue, host127.0.0.1, port8050)关键设计点dbc.Container(fluidTrue)启用流体布局适配不同屏幕宽度dcc.Slider和dcc.Dropdown提供直观筛选入口callback装饰器定义输入输出关系Input监听用户操作Output更新图表go.Figure()直接复用Plotly语法无缝迁移现有可视化代码debugTrue开启热重载修改Python代码后浏览器自动刷新。5.3 报告导出技巧用plotly.io.write_html生成离线HTML嵌入SHAP力导向图业务方常需将分析结果发邮件或存档。Dash本身不提供导出按钮但可通过plotly.io.write_html将单个图表保存为独立HTML文件再用iframe嵌入报告import plotly.io as pio # 生成SHAP力导向图 shap.plots.force(explainer.expected_value, shap_values[0], X_test_sample[0], feature_names[temp, humidity, is_holiday], matplotlibFalse, showFalse) # 保存为HTML pio.write_html( shap.plots.force(explainer.expected_value, shap_values[0], X_test_sample[0]), fileshap_force_plot.html, auto_openFalse ) # 在Dash中用html.Iframe嵌入 html.Iframe( srcDocopen(shap_force_plot.html).read(), style{width: 100%, height: 400px, border: none} )此方法生成的HTML文件双击即可在任意浏览器打开包含完整交互功能悬停显示数值、缩放平移真正实现“一份代码多端复用”。6. 生产环境避坑指南内存泄漏排查、模型版本管理与增量预测流水线6.1 Selenium内存泄漏用contextlib管理驱动生命周期避免Chrome进程堆积长时间运行爬虫时未关闭的ChromeDriver进程会持续占用内存。解决方案是用contextlib.contextmanager封装驱动确保异常时也释放资源from contextlib import contextmanager contextmanager def get_chrome_driver(): driver None try: driver init_driver() yield driver finally: if driver: driver.quit() # 关键必须调用quit()而非close() # 使用方式 with get_chrome_driver() as driver: session get_session_with_cookies(driver, https://example.com/) data fetch_paginated_data(session, api_url) # 退出with块时自动执行driver.quit()driver.quit()关闭整个浏览器会话并释放所有关联进程driver.close()仅关闭当前标签页残留进程仍在后台运行。6.2 模型版本控制用joblib保存scaler与LSTM权重用MLflow追踪超参数每次训练LSTM都会产生新权重若未保存下次运行将丢失最佳模型。本项目采用分层保存策略import joblib from tensorflow.keras.models import load_model # 保存归一化器和模型 joblib.dump(scaler, models/scaler.pkl) model.save(models/lstm_model.h5) # Keras原生格式兼容性最好 # 加载时 scaler joblib.load(models/scaler.pkl) model load_model(models/lstm_model.h5)注意model.save()保存完整模型含架构、权重、优化器状态比model.save_weights_only()更稳妥joblib比pickle对NumPy数组序列化效率更高。6.3 增量预测流水线用Airflow调度每日任务用SQLite存储预测结果真实场景需每日自动运行爬取新数据 → 更新模型 → 生成预测 → 推送报告。本项目用轻量级方案实现组件用途配置要点schedule.pyPython脚本封装全流程用subprocess.run()调用爬虫、训练、可视化脚本airflow dag每日02:00触发schedule_interval0 2 * * *results.dbSQLite存储预测表表结构id, date, city, actual, predicted, rmse-- results.db中创建预测表 CREATE TABLE predictions ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, city TEXT NOT NULL, actual REAL, predicted REAL, rmse REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );每次预测结果插入此表Dash仪表盘直接查询最新数据形成“采集-建模-预测-展示”闭环。无需复杂消息队列单机SQLite完全满足中小规模业务需求。本文还有配套的精品资源点击获取
返回列表