1. 项目全景拆解这个毕设到底在做什么1.1 选题价值与核心亮点先说结论随机森林 Django Vue这个组合做空气质量指数预测系统是一套性价比极高、答辩上限也很高的毕业设计选题。它既不是纯算法项目也不是纯 CRUD 管理系统而是把“机器学习模型训练”和“Web 可视化平台”串成了完整闭环——用户在浏览器里看到的每一个预测曲线、每一张空气质量等级卡片背后都连着真实的历史污染物数据和训练好的回归模型。这个项目能解决的问题很明确基于过去一段时间的气象数据、污染物浓度数据预测未来某个时刻的 AQIAir Quality Index空气质量指数并实时展示当前空气质量等级、主要污染物贡献占比、趋势走向。它适合两类人参考一类是计算机/大数据专业的学生想做一个能同时体现算法能力和工程能力的毕设另一类是已经工作但想把机器学习应用层打通的开发者拿这个项目当练手模板。我最喜欢这个选题的地方在于它“够具体”。空气质量和每个人相关数据公开可获取业务逻辑不绕弯但又有足够的技术纵深可以挖掘——特征怎么构造、缺失值怎么补、模型怎么调参、前端怎么把高维数据可视化每一环都有话可讲。答辩时评委随便问一个细节你都能接得住。1.2 谈点实在的随机森林和“深度学习”到底是什么关系这里必须先纠一个很多人会踩的坑。题目里写“大数据深度学习算法”但随机森林本身不属于深度学习。随机森林是集成学习Ensemble Learning里的 Bagging 类算法基学习器是决策树深度学习指的是多层神经网络自动学习特征表示典型代表是 CNN、RNN、Transformer 这类结构。很多学生做毕设的时候容易把这两个词混用甚至直接把“机器学习”笼统叫成“深度学习”答辩时被老师一问就容易卡壳。我的建议是在论文和 PPT 里用词严谨一点写清楚“随机森林是机器学习中的集成学习算法”即可。如果你确实想让“深度学习”三个字站得住脚可以考虑做模型对比实验——用随机森林和 MLP多层感知机分别训练同一个数据集然后用图表对比 R²、MAE 等指标这样深度学习和随机森林就都覆盖到了答辩时既有的聊也显得你知识面广。至于“大数据”这个概念在这个项目里更像是“大数据分析与可视化”的教学场景而不是 Hadoop/Spark 那套分布式技术栈。这个项目的数据量通常在几万到几十万行量级单机跑随机森林没有问题。你可以在论文里把“大数据”定位成“多维度环境监测数据的大规模分析与挖掘”而不是真的去搭一个分布式集群——既省时省力逻辑上也合理。1.3 技术选型为什么是DjangoVue前后端分离是现在 Web 项目的绝对主流。Django 负责后端 API 和数据持久化Vue 负责前端页面渲染和交互两者通过 JSON 接口通信。这个组合的优势有三点第一Django 自带 ORM 和 Admin 后台开发效率极高。空气质量数据天然适合关系型数据库存储Django 的 models 写起来快Admin 还能白嫖一个数据管理后台毕设演示时非常加分。第二Vue 的组件化开发适合做数据可视化看板。预测系统的核心交互是“选城市-看曲线-看等级”这本质上是组件状态管理的问题Vue 的响应式数据流配合 ECharts 图表库能非常舒服地把数据映射成视图。第三Python 生态直接贯通。Django 是 Python 框架随机森林用 scikit-learn 训练两者之间不需要跨语言桥接模型保存、加载、预测的代码写起来一气呵成。如果换 Java Spring Boot你还要用 PMML 或者单独起一个 Python 微服务来部署模型平白增加复杂度。关于前端工具链我建议用 Vue 3 Vite。相比 Vue 2 的 Webpack 配置Vite 的开发服务器启动速度是秒级的热更新也快能省下大量等待时间。2. 核心算法拆解随机森林如何预测AQI2.1 随机森林原理与“为什么选它”随机森林回归的本质是训练多棵决策树然后让所有树对预测结果取平均。每一棵决策树都在原始数据集的随机抽样子集上训练同时每次节点分裂时只看随机挑选的部分特征来寻找最优切分点。这两个“随机”叠加在一起使得每棵树都长得不一样单棵树可能过拟合但一森林平均下来方差就被压低了。很多人把随机森林和决策树搞混其实核心差异就在这。单棵决策树深度一大就特别容易过拟合——训练集上指标漂亮一换数据就崩。随机森林通过 BaggingBootstrap Aggregating机制做样本扰动又通过随机特征选择做特征扰动双管齐下控制过拟合。打个比方一个人预测明天空气质量可能凭感觉走偏但一百个背景各异的人投票取平均结果就会稳健得多。AQI 预测这个场景非常适合随机森林。第一空气质量数据特征维度中等七八个污染物浓度加气象特征没有到图像、文本那种需要深度网络自动提特征的程度第二AQI 和各污染物浓度之间存在较强的非线性关系决策树不用像线性回归那样手动构造交互项树模型天然能切分出复杂模式第三训练快、调参少、可解释性强——scikit-learn 里可以直接输出特征重要性答辩时讲“PM2.5 和 PM10 是影响 AQI 的最主要特征”时是有数据支撑的。2.2 特征工程AQI预测到底喂什么数据AQI 预测不是直接把过去几天的 AQI 扔给模型就完事。一般会构造三部分特征。污染物浓度特征PM2.5、PM10、SO₂、NO₂、CO、O₃ 这六项是计算 AQI 的直接原料。注意AQI 不是六项的平均值而是先分别计算每一项的 IAQIIndividual Air Quality Index分指数再取最大值。所以模型学到的核心映射关系就是这些浓度值到分指数再到总分指数的计算逻辑。随机森林对这种固定函数关系的学习能力很强只要数据质量没问题这部分特征给足就能保证模型底线。气象特征温度、湿度、风速、风向、气压、降水量这些特征反映污染物扩散条件。比如风速大、降水多的时候空气质量通常好逆温层存在时污染物容易累积。加入气象特征能让模型学习到污染物浓度之外的因果线索。时间特征小时、星期几、是否节假日、是否采暖季。空气质量有明显的周期性——一天之内早晚高峰污染较重秋冬采暖季比夏季差。把时间维度编码成特征模型就能捕捉到这些周期性规律。有一个关键细节预测时刻的污染物浓度无法直接作为输入特征。假设你要预测明天上午 10 点的 AQI可以用今天上午 10 点、昨天上午 10 点的数据作为滞后特征但不能直接把“明天 PM2.5 浓度”喂进去否则就是数据泄漏。正确做法是用过去 N 小时的滑动窗口统计量——均值、最大值、变化趋势作为预测依据。2.3 模型评估指标与调参实践回归任务评估不看准确率看以下三个指标R²决定系数模型解释了多少比例的数据方差越接近 1 越好。一般 AQI 预测做到 0.85 以上就算不错。MAE平均绝对误差预测值和真实值差值的绝对值取平均单位是 AQI 指数。MAE 在 15-20 左右说明预测偏差不到一个空气质量等级已经具备参考价值。RMSE均方根误差对大误差更敏感因为先平方再开方会把偏差大的样本暴露出来。如果 RMSE 明显大于 MAE说明存在少量预测特别离谱的样本。随机森林的调参优先级是这样的先定n_estimators树的数量从 100 开始画学习曲线看 RMSE 是否收敛再调max_depth最大深度限制深度能有效防止单棵树过拟合最后微调min_samples_split和min_samples_leaf来控制树的细化程度。我把常用参数范围整理在下面参数常调范围作用n_estimators100 – 500树越多越稳但超过一定值收益递减max_depth10 – 30 或 None限制树深度值越小越保守min_samples_split2 – 10分裂所需最小样本数增大可防过拟合min_samples_leaf1 – 5叶节点最小样本数增大可提高泛化性max_featuresauto / sqrt每次分裂随机选的特征数默认即可random_state42固定随机种子保证可复现实操中我一般先固定其他参数将n_estimators调到损失曲线平台期再去网格搜索max_depth和min_samples_split。有时候你以为是小参数结果对结果的影响反而很大。2.4 一段可以直接跑的模型训练代码下面是训练随机森林回归模型的核心代码。这个代码片段可以直接放在 Django 项目的ml_train.py脚本文件里独立于 Web 服务运行import pandas as pd from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error df pd.read_csv(aqi_train_data.csv) feature_cols [ pm25_lag1, pm10_lag1, so2_lag1, no2_lag1, co_lag1, o3_lag1, temperature, humidity, wind_speed, pressure, hour, day_of_week, is_heating_season ] X df[feature_cols] y df[aqi] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, shuffleFalse ) model RandomForestRegressor( n_estimators300, max_depth20, min_samples_split4, min_samples_leaf2, random_state42, n_jobs-1 ) model.fit(X_train, y_train) y_pred model.predict(X_test) print(R2:, r2_score(y_test, y_pred)) print(MAE:, mean_absolute_error(y_test, y_pred)) print(RMSE:, mean_squared_error(y_test, y_pred, squaredFalse)) # 特征重要性输出 for name, imp in zip(feature_cols, model.feature_importances_): print(f{name}: {imp:.4f})注意shuffleFalse时间序列数据不能像普通分类任务那样随机打乱再划分否则会出现“拿明天预测今天”的数据泄漏测试集永远“看起来很美”一到真实场景就翻车。这是时间序列预测里最容易犯的错误也是答辩时老师最爱挖的坑。3. 后端Django从ORM到模型服务化3.1 项目骨架与数据库模型设计Django 的后端工程建议按应用拆分。我习惯建三个 appdatasets负责原始数据和特征数据的管理prediction负责调用模型、产出预测结果visualization负责向前端聚合输出看板数据。数据库模型方面空气质量数据适合拆成几张表。核心的数据表结构我建议这样设计# apps/datasets/models.py from django.db import models class City(models.Model): name models.CharField(max_length50, uniqueTrue) code models.CharField(max_length20) class PollutantRecord(models.Model): city models.ForeignKey(City, on_deletemodels.CASCADE) timestamp models.DateTimeField(db_indexTrue) pm25 models.FloatField(nullTrue) pm10 models.FloatField(nullTrue) so2 models.FloatField(nullTrue) no2 models.FloatField(nullTrue) co models.FloatField(nullTrue) o3 models.FloatField(nullTrue) aqi models.IntegerField(nullTrue) temperature models.FloatField(nullTrue) humidity models.FloatField(nullTrue) wind_speed models.FloatField(nullTrue) pressure models.FloatField(nullTrue) class Meta: unique_together (city, timestamp) class PredictionResult(models.Model): city models.ForeignKey(City, on_deletemodels.CASCADE) timestamp models.DateTimeField(db_indexTrue) predicted_aqi models.FloatField() actual_aqi models.FloatField(nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue)这里有几个设计细节值得说。第一PollutantRecord里把 AQI 和污染物浓度放在同一张表因为建模时需要同时用到它们第二co字段单位是 mg/m³其他污染物是 μg/m³单位不统一在计算 IAQI 时要小心换算建模时建议统一做归一化第三PredictionResult单独建表把预测值和真实值未来回填放在一起方便后续评估模型在真实场景中的表现。3.2 模型训练侧与预测侧的分离很多人把训练和预测写在一个视图函数里这个习惯很不好。随机森林训练需要完整数据集可能要花几秒到几十秒每次请求都重新训练系统直接瘫掉。正确思路是训练离线做预测在线做。训练侧用脚本完成从数据库读取历史数据构造特征集训练模型然后用joblib.dump把模型以二进制文件形式保存到项目目录下的models/文件夹里。# ml_train.py项目根目录脚本 import joblib # ... 上述随机森林模型训练代码 ... joblib.dump(model, models/aqi_rf_v1.pkl) joblib.dump(feature_cols, models/aqi_feature_cols.pkl)预测侧在 Django 视图里加载已保存的模型文件做特征预处理和推理# apps/prediction/views.py import joblib import numpy as np from django.http import JsonResponse from django.utils import timezone from datetime import timedelta from ..datasets.models import PollutantRecord model joblib.load(models/aqi_rf_v1.pkl) feature_cols joblib.load(models/aqi_feature_cols.pkl) def predict_next(request, city_id): city_id int(city_id) now timezone.now() lag1_records PollutantRecord.objects.filter( city_idcity_id, timestamp__date(now - timedelta(days1)).date() ) if not lag1_records.exists(): return JsonResponse({code: 400, msg: no enough data}, status400) # 构造特征向量注意特征顺序必须与训练时一致 latest lag1_records.latest(timestamp) features np.array([[ latest.pm25, latest.pm10, latest.so2, latest.no2, latest.co, latest.o3, latest.temperature, latest.humidity, latest.wind_speed, latest.pressure, now.hour, now.weekday(), 1 if (now.month in (11, 12, 1, 2, 3)) else 0 ]]) pred model.predict(features)[0] result PredictionResult.objects.create( city_idcity_id, timestampnow, predicted_aqifloat(pred) ) return JsonResponse({ code: 200, data: { city_id: city_id, timestamp: now.strftime(%Y-%m-%d %H:%M), predicted_aqi: round(float(pred), 1), level: aqi_level(float(pred)), } })这个过程中最容易出错的就是特征顺序不一致。训练时的特征列是pm25, pm10, so2, no2, co, o3, temperature...预测时也必须按照完全相同的顺序排列否则模型拿到的特征语义错位预测结果毫无意义。所以我推荐把feature_cols列表和模型一起joblib.dump保存预测时直接读取避免手敲特征顺序导致出错。3.3 API接口设计与前端数据契约前后端分离项目必须定义清楚接口契约。我用 Django REST FrameworkDRF写接口因为它自带序列化器和分页省去很多手工 JsonResponse 的麻烦。核心接口我设计成这样接口名路径方法用途城市列表/api/cities/GET返回所有支持的城市历史数据/api/cities/{id}/history/GET返回污染物浓度时序数据预测结果/api/cities/{id}/predict/POST触发预测返回预测 AQI模型指标/api/model/metrics/GET返回 R²、MAE 等指标接口返回统一用{code, message, data}包裹code为 200 表示成功其他为失败。前端拿到非 200 状态码时直接弹错误提示不解析 data。这样做的目的是把错误处理逻辑统一收口到拦截器里避免每个页面重复写try-catch。这里要特别提醒一下 DRF 配 CORS 的问题。开发阶段前端运行在http://localhost:5173后端在http://localhost:8000端口不同即跨域。需要在settings.py里配置django-cors-headersINSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # 要在 CommonMiddleware 之前 ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, http://127.0.0.1:5173, ]跨域问题不解决前端会一直报 “Blocked by CORS policy”而且报错信息容易误导你以为是接口挂了。这个坑几乎必踩先把它配好再写业务代码。4. 前端Vue可视化看板与交互细节4.1 工程搭建与项目目录规划前端我用 Vue 3 Vite Vue Router Pinia Axios Element Plus ECharts这套组合是目前 Vue 生态最主流的选择不用自己造轮子。创建项目npm create vitelatest aqi-frontend -- --template vue cd aqi-frontend npm install npm install axios vue-router4 pinia element-plus echarts依赖装完之后把 vite.config.js 里的server.proxy配置好。开发环境下让 Vite 把/api请求代理到 Django这样前端代码里统一写相对路径/api/...不需要关心后端地址是 8000 还是别的端口也顺带绕开了 CORS// vite.config.js export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } })项目目录里src/api放接口封装src/views放页面组件src/components放公共组件。我用 Pinia 管理城市维度和预测结果的全局状态这样从“城市选择页”跳到“看板页”时选中的城市信息不会丢失。4.2 页面规划与核心交互逻辑整个前端规划了三个核心页面。首页概览页进入系统后的默认页展示全国或选定区域各城市今天的实时 AQI 排名、空气质量等级分布饼图、整体变化趋势折线图。这页的数据来自历史数据接口前端拿到数据后按 AQI 从低到高排序颜色按等级映射——绿色优、黄色良、橙色轻度污染、红色中度污染、紫色重度污染、褐红色严重污染。城市详情页路由形如/city/:id展示某一个城市近 14 天的 AQI 趋势曲线下面用多个折线图分别展示 PM2.5、PM10、SO₂、NO₂、CO、O₃ 的浓度变化再配一个雷达图展示各污染物相对浓度水平。这一页是信息量最大的也是答辩演示的主角。预测对比页调用预测接口展示“未来 24 小时 AQI 预测”的结果——预测值、对应空气质量等级、置信度提示。我建议把模型在历史数据上的预测值和真实值画在同一张图里做对比让用户直观看到模型的准确性。这个页面最能体现随机森林模型的价值。组件间的数据传递我统一走 Pinia store不在路由参数里塞复杂对象。路由只传 cityId其他数据全部异步拉取。这样刷新页面后还能正常恢复状态不会出现“路由有参数但 store 里没数据”的尴尬。4.3 ECharts可视化如何接真实数据ECharts 是前端可视化的核心。画图本身不难难的是怎么把接口数据转换成 ECharts 期望的数据结构。我封装了一个fetchHistory方法// src/api/index.js import axios from axios const client axios.create({ baseURL: /api, timeout: 10000 }) export function fetchCityHistory(cityId, days 14) { return client.get(/cities/${cityId}/history/, { params: { days } }).then(res res.data.data) }页面组件里拿到返回的数组后按照 ECharts 的 xAxis 和 series 结构做映射。核心代码// src/views/CityDetail.vue import * as echarts from echarts import { fetchCityHistory } from ../api import { onMounted, ref } from vue const aqiChartRef ref(null) async function loadChart() { const data await fetchCityHistory(cityId, 14) const chart echarts.init(aqiChartRef.value) chart.setOption({ title: { text: 近14天AQI变化趋势, left: center }, tooltip: { trigger: axis }, xAxis: { type: category, data: data.map(item item.timestamp.slice(5, 10)) }, yAxis: { type: value, name: AQI指数 }, series: [{ name: 实际AQI, type: line, smooth: true, areaStyle: { opacity: 0.2 }, data: data.map(item item.aqi), markLine: { data: [ { yAxis: 50, lineStyle: { color: #e6c300 } }, { yAxis: 100, lineStyle: { color: #ff8c00 } } ] } }] }) } onMounted(loadChart)两个细节经验。第一chart实例要在组件卸载时dispose否则在 Vue 路由切换时图表会占用内存不释放页面开久了会卡顿第二ECharts 在容器尺寸变化时不会自动重绘如果页面有侧边栏折叠这类布局变化需要在nextTick里调用chart.resize()否则图表会出现显示不全的问题。5. 数据从哪来数据获取与预处理实录5.1 公开数据集和爬虫方案怎么选空气质量预测项目的数据获取有三个可选路径。最省事的是用公开数据集。中国环境监测总站发布的全国城市空气质量数据、UCI 机器学习库里的 Air Quality 数据集都是可以拿来直接用的。学术公信力强来源写清楚答辩时能站住脚。第二是爬虫方案爬取空气质量历史数据网站。Python 用requests BeautifulSoup或者Selenium都能实现。这个方案的优点是数据量可以做得很大、覆盖的城市范围广缺点是有一定的编码和运维成本网站页面结构一变爬虫就崩。我在工程里会写一个通用的采集类用 CSS 选择器解析列表页和详情页并把采集到的数据写入 Django 的PollutantRecord表。第三种方案是模拟数据兜底。如果目标爬取站点临时挂了或者某个测试阶段不要求真实数据可以使用numpy生成带噪声的模拟数据——设置基础浓度水平、加周期性波动和随机噪声生成时间序列。这个方法在开发联调阶段特别有用能让前后端开发不被数据问题阻塞。5.2 缺失值处理和环境数据对齐空气质量监测数据有个痛点缺失和异常太多。设备维护、网络中断、极端天气都会导致某一天的某个污染物浓度没有记录。如果直接把这个样本丢给模型特征矩阵就会出现 NaN训练直接报错。我的处理策略分三步。第一步删除所有特征列同时缺失超过 30% 的样本这种样本信息量太低补出来的也是噪声。第二步对单特征缺失用最近时刻的有效值进行前向填充因为污染物浓度在相邻时间点的取值有连续性用后向填充反而会引入未来信息第三步对还没补上的极个别样本显示插值多数情况下几行插值不影响模型整体效果。数据对齐是另一个容易出问题的地方。污染物浓度数据发布通常是每小时一条但气象数据可能每天固定几个时刻如果你们是拿的气象站数据就需要把时间戳统一到同一粒度。实际做法是把气象数据按小时做了重采样风速取整小时平均、温度取整小时平均、降水量取累计值。5.3 训练集/验证集划分的隐藏陷阱前面提到时间序列数据不能随机 shuffle我再把这个坑展开讲透。假设你用train_test_split(X, y, test_size0.2, random_state42)做默认划分数据会被随机打乱模型看到的训练集里可能包含未来时间的数据测试集里反而包含过去时间的数据。由于污染物浓度具备时间连续性模型表面上拿到了 0.98 的 R²实际部署后预测能力惨不忍睹。正确做法有两个。第一是保留时间顺序划分前 80% 时间作为训练集、后 20% 作为测试集用shuffleFalse参数实现。第二个更好的做法是使用TimeSeriesSplit做交叉验证它在不断扩展训练集的同时保持测试集永远在未来。用这个方式评估出的指标才代表模型的真实泛化能力。这部分内容我建议在论文里重点写答辩时说出来非常加分——几乎所有时间序列预测项目的数据泄漏问题都在这里。另一个隐患是特征的时间对齐。如果特征是前一天的污染物浓度而标签是当天的 AQI两者在数据库里查询拼接时必须保证PollutantRecord中每条记录的时间戳精确对齐。我曾经因为时区设置不一致导致某城市所有特征整体偏移了一个小时模型 RMSE 高到离谱排查了半天才在数据库里发现了时区错乱。Django 项目里TIME_ZONE Asia/Shanghai和USE_TZ True必须配套配好取数据时统一用本地时间。6. 常见问题与排查技巧实录6.1 DjangoVue联调时的CORS和端口问题联调环节我遇到最多的问题就是 CORS。明明后端接口用浏览器直接访问没问题前端axios一请求就报跨域错误。常见原因django-cors-headers中间件位置不对或者CORS_ALLOWED_ORIGINS忘记加端口号。解决办法是先在浏览器直接访问http://localhost:8000/api/cities/确认后端正常再检查中间件配置是否在CommonMiddleware之前。还有一个小细节开发时如果用npm run dev起的 Vite 服务是 5173 端口如果改了端口要同步更新配置。另外如果你把 Django 配在 8000 端口、Vue 配在 5173 端口但同一个网络环境下有其他服务端口冲突启动时会有OSError: [Errno 98] Address already in use的报错。直接用lsof -i :8000查占用进程再结束即可不用重装饰端口。因为两个端口只是开发环境临时方案联调完成后部署时统一走 Nginx 反向代理就不存在跨域问题。6.2 模型训练集指标好但预测趋势不对有段时间我做出来的模型在测试集上 R² 有 0.9但把预测曲线和真实曲线画在一起发现预测值总是比真实值滞后一两个时间点而且整体被“拉平”。这类问题典型的原因是时间序列的自相关性模型学到的最有效特征往往是前一天的 AQI 值所以预测结果近似于把昨天的值搬过来趋势当然滞后。解决办法是减少对滞后标签的依赖增加外生特征——气象条件、季节、节假日等跟历史自回归信息弱相关的特征。我当时把未来一天的天气预报特征加入预测条件后趋势跟随现象缓解不少。另外也可以考虑换用序列建模思路比如 LightGBM 加滞后特征窗口或做一阶差分让模型学习的是“变化量”而不是“绝对值”效果往往更好。6.3 Vue路由刷新404与图表样式错乱Vue 项目部署到服务器后用户点击浏览器刷新按钮结果页面直接 404。这是因为前端路由是 history 模式的 client-side routing页面切换不需要请求服务器但刷新时会向服务器发出对应的 URL 请求而后端服务器上根本没有这个路径对应的真实文件。解决办法是配置 Nginx 的 try_files 指令将所有路由请求都回退到index.htmllocation / { root /var/www/aqi-frontend/dist; index index.html; try_files $uri $uri/ /index.html; }图表样式错乱的坑更容易被忽略ECharts 图表渲染之后如果异步加载的数据比组件挂载更慢容器高度可能还没被 CSS 撑开导致图表压缩成一小块。解决方法是给图表的容器设置固定高度比如height: 400px或者监听数据加载完成后再调用chart.resize()。6.4 环境版本兼容性的血泪教训Django、Python、scikit-learn、Node.js 的版本兼容问题我踩过不少坑整理成速查表如下症状原因解法pip install 报依赖冲突Python 3.12 与部分旧库不兼容用 Python 3.10明确写 requirements.txtsklearn 模型保存后跨版本加载报错版本间 pickle 格式变动训练和预测必须用同一套环境npm install 报 engine 不匹配Node 版本过老或过新用 nvm 统一 Node 18Django 4 里 pymysql 配置报错需要显式调用 pymysql.install_as_MySQLdb()在项目__init__.py里加上Vue 3 项目里引入 Vue 2 组件库报错Element UI 和 Element Plus 不兼容确认安装的是 element-plus 而非 element-ui我的建议是创建项目后第一时间导出环境依赖后端pip freeze requirements.txt前端确认package-lock.json入库。这样即使换电脑重装环境也能几分钟内恢复不会因为版本不一致白耗一整天。6.5 常见问题快速定位表把开发中积攒的问题整理成速查表遇到类似情况可以先对照现象排查步骤最终处理前端请求报 500看 Django 日志检查是否数据为空捕获异常返回空数组或提示模型预测结果全为同一个值特征顺序错位或特征全部缺失比对 feature_cols 顺序检查 NaN前端图表不显示检查接口是否返回、data 字段是否存在在 axios 拦截器里打印 res.data.data部署后静态文件 404Django 静态文件未 collectstaticpython manage.py collectstatic跨域请求 OPTIONS 报 403CORS 配置未加载检查 Middleware 顺序和 allowed origins写在最后踩过这么多坑之后我对这个项目最深的体会是毕设能不能拿高分往往不取决于模型有多高级而取决于你的工程链路有多完整。随机森林本身是个标准算法你只要把“数据获取-特征构造-模型评估-可视化呈现”这条链路走通把每一步里的权衡和取舍说清楚就已经超过大多数只交个 notebook 的选手。最后分享一个我自己答辩前做的小动作把随机森林模型的特征重要性排序做成一张带颜色渐变的条形图放在系统首页最显眼的位置。这张图能直观地回答“为什么用随机森林”这个问题——你一眼就能看到 PM2.5、PM10 对 AQI 贡献最大模型决策依据一目了然。很多时候评委不是要你证明算法比别人好而是想确认你真的理解了模型在做什么。这个项目后续再想扩展可以接实时爬虫数据流、加入更多城市的对比、接入短信预警推送——但先把地基打好每一层都做得扎实比什么都重要。