基于Python的天气预测课程设计:从数据清洗到可视化全流程
简介基于Python的天气预测与可视化项目源码面向高校Python课程设计或期末大作业场景覆盖天气数据采集、清洗、建模、预测与可视化完整流程无需修改即可运行适合具备基础Python语法、希望完成数据分析和可视化综合项目的学生作为高分结课参考。压缩包共24个文件大小约1.42MB核心包括4个Python脚本数据采集、数据处理、模型训练、主程序、4个CSV天气数据集、已训练好的Model.pkl模型文件、一个HTML天气展示页面以及12张结果图表和readme使用说明目录结构便于按模块查阅。已有575人学习下载。项目附带的说明文档和可视化结果图能帮助快速对照运行效果数据与模型齐全可深入理解天气预测从数据清洗到结果展示的实现细节也便于在此基础上二次扩展或改写完成课程设计报告。1. 从天气数据到预测结果这个项目的真实边界很多拿到“天气预测”作业的人第一反应是直接上 LSTM但真正能拿高分的课程设计胜在数据链路完整而不是模型有多复杂。这个项目用 Python 把数据采集、清洗、建模、可视化串成了一条可运行的主线比较适合期末大作业或者课程设计拿来当骨架改一改就能应付答辩。项目里真正有含金量的不是某个算法而是那套分工明确的关系GetData.py 负责把天气数据抓下来ProcessData.py 负责清洗和特征构造GetModel.py 训练好模型并保存成 Model.pkl最后 main.py 加载模型把预测结果渲染到天气网.html 页面上。理解了这些脚本之间的数据传递关系即使不改一行代码也能把答辩讲清楚顺带知道怎么扩展城市、怎么更新模型。2. 数据链路GetData.py 与三个 CSV 的时间序列拼图2.1 三个 CSV 的分工训练集、测试集与全国实况打开项目后第一眼值得注意的就是 date_train.csv、date_test.csv 和 china_today.csv 这三个文件。date_train.csv 是历史训练样本里面保存着带历史真实观测值的天气记录用来训练模型date_test.csv 是准备预测的样本特征结构与训练集一致但没有完整的历史标签或者标签留作验证用china_today.csv 则像一张“全国今日天气快照”为可视化页面提供地理维度的数据来源比如地图上每个城市的当前温度、湿度、天气现象。这三个文件不能混用。最常见的问题是把 china_today.csv 当训练集用训练时看着没报错但实际上它缺失大量历史字段预测结果完全不可解释。动手第一步应当是读入文件确认数据类型而不是直接跑 main.py。下面这段代码建议每次打开项目先执行一遍import pandas as pd # 用 parse_dates 把 date 列转成 datetime后面才能按时间做特征 train pd.read_csv(date_train.csv, parse_dates[date]) test pd.read_csv(date_test.csv, parse_dates[date]) today pd.read_csv(china_today.csv) # 看字段类型与缺失值先摸清数据长什么样 print(train.dtypes) print(test.columns) print(today.head())参数说明parse_dates[date]会在读入时自动将日期字符串解析为 datetime 类型比事后pd.to_datetime更省一步打印dtypes是为了发现哪些列仍然是 object 类型那些列往往是后续清洗的重点区域。日期特征决定了一个天气预测项目能不能按“时间序列”去处理如果这一步漏掉后面构造“月份”“星期”之类的特征会直接报错。从结构上看这个项目不是多个 CSV 的无序堆放而是有一个隐性的时间线train 是过去test 是未来today 是当前状态。答辩时能说出这一条分数起点就不一样。2.2 清洗与类型转换pandas 能容忍的脏数据长什么样爬虫拿到的天气数据里面最容易出问题的不是格式错误而是“看起来是数字实际是字符串”。比如接口返回的null值被保存成None湿度字段混入100.0%温度字段出现--。这时候直接用int()转换会抛异常正确做法是用 pandas 的pd.to_numeric以宽松模式清洗。ProcessData.py 里应该承担这层职责把原始 CSV 转成模型能直接读取的数值型 DataFrame。以下是常见的清洗规则对应代码可以直接用在课程设计里# 将字符串型数字转成浮点数转换失败的位置保留 NaN train[temp_max] pd.to_numeric(train[temp_max], errorscoerce) train[temp_min] pd.to_numeric(train[temp_min], errorscoerce) # 删掉没有温度的样本保留缺失湿度等次要字段的样本 train train.dropna(subset[temp_max, temp_min]) # 物理范围过滤湿度 0~100温度 -50~55 train train[(train[humidity] 0) (train[humidity] 100)] train train[(train[temp_max] -50) (train[temp_max] 55)]这段代码中的errorscoerce是核心参数它的含义是把无法解析的值置为 NaN而不是让程序中断随后用dropna(subset...)只删除目标列缺失的样本而不是把含有任何 NaN 的行全部删掉。这样做的好处是保留了一部分湿度缺失但温度正常的样本让训练样本数量最大化。筛选湿度范围时注意边界值湿度等于 100 在高湿度地区是合理数据不能因为过滤条件写成100而误删温度范围也要根据项目所覆盖的城市调整。如果项目只做南方城市-50 这种下限基本没有影响但放到北方冬季城市时低于 -40 的历史数据确实存在过滤条件不能拍脑袋写死。这一节不仅解决“能不能跑”还能解释为什么训练出的模型对异常值不敏感。2.3 特征构造把“日期”和“天气”变成模型能读的列原始 CSV 里最常见的列有 date、temp_max、temp_min、humidity、pressure、weather 这类字段其中 weather 是中文文本比如“晴”“多云转阴”。直接把字符串喂给 sklearn 会报错所以要做特征编码。日期字段也需要拆开因为“月份”“是否周末”对温度有相关性而“完整日期字符串”是无法参与数值计算的。常见做法是把日期拆成年、月、日、星期几、是否周末再做一列滞后温度。滞后特征意味着用前一天的观测值预测今天这对天气这样的连续过程非常有效。实现如下# 必须先按日期排序再构造滞后特征 train train.sort_values(date).reset_index(dropTrue) train[year] train[date].dt.year train[month] train[date].dt.month train[day] train[date].dt.day train[weekday] train[date].dt.weekday train[is_weekend] train[weekday].isin([5, 6]).astype(int) # 天气文本转成分类编码 train[weather_code] train[weather].astype(category).cat.codes # 用前一天的最高温作为新特征NaN 会自动出现在每个序列的第一行 train[temp_max_lag1] train[temp_max].shift(1)参数说明sort_values(date)必须先执行否则shift(1)取到的“前一天”并不是真正的时间前一位而是当前文件里的上一行这在多城市数据混排时会引入跨城市错误导致预测结果异常好或者异常差。astype(category).cat.codes是 Python 类型转换里比较隐晦的一种它将类别文本映射成整数编码注意这个编码顺序不一定有大小含义但在树模型里完全可以接受。对特征有个基本判断month、weekday 这类周期性特征能帮模型区分季节和周末效应lag1 特征让模型学到“今天和昨天温度不会差太多”这条朴素规律。如果你的 date_train.csv 足够长甚至可以构造 lag2、lag3但要注意每个滞后特征都会拿走一行有效样本特征多了样本少反而得不偿失。3. 天气预测模型GetModel.py 训练了什么Model.pkl 里存了什么3.1 模型选型为什么不是神经网络也能拿高分项目里既然生成了 Model.pkl说明核心预测部分不是逻辑代码而是用一个机器学习模型保存下来的权重或结构。对于这种量级的课程设计我不建议一上来就套 LSTM 或 Transformer原因有三第一天气预测需要可解释性随机森林可以直接输出特征重要性答辩时能指着特征排名讲第二训练数据量通常只有几百到几千行深度学习很容易过拟合第三神经网络的训练时间不可控课堂演示环境不一定能复现结果。这个项目最合理的选型是随机森林或梯度提升树它们能处理非线性关系、对缺失值有一定容忍度并且训练速度快。用Model.pkl这个文件后缀也能看出模型是用 joblib 或 pickle 保存的这正好是 scikit-learn 生态的常见保存方案。随机森林用于回归时预测值是所有决策树结果的平均值因此相比单棵决策树稳定很多。3.2 训练与持久化从 DataFrame 到 joblib 文件GetModel.py 的核心任务可以拆成三部分读取 date_train.csv选择特征列训练模型并保存。这里给出一个可以直接套在项目上的训练骨架import joblib from sklearn.ensemble import RandomForestRegressor # 特征列来自 ProcessData 构造好的字段 feature_cols [month, day, weekday, humidity, pressure, weather_code, temp_max_lag1] X train[feature_cols] # 预测目标是当天最高温 y train[temp_max] # 有缺失的样本需要剔除尤其是第一行 lag1 缺失 train_clean train.dropna(subsetfeature_cols [temp_max]) X train_clean[feature_cols] y train_clean[temp_max] model RandomForestRegressor( n_estimators200, max_depth8, min_samples_leaf3, random_state42 ) model.fit(X, y) joblib.dump(model, Model.pkl)参数说明n_estimators200是决策树的数量数量越多预测越稳定但训练时间和文件体积也会上升max_depth8限制每棵树的深度防止单个特征把分支切得过细min_samples_leaf3规定每个叶子节点至少保留 3 个样本这是一种常见的正则化手段。random_state42保证每次运行生成完全相同的随机森林否则两次训练结果不一致答辩演示时容易出现“上一次跑出一个结果这次跑出另一个结果”的尴尬情况。模型保存和加载必须注意文件路径。joblib.dump(model, Model.pkl)保存的对象不只有模型参数还包括模型内部的类别编码和特征名称如果之后移动 HTML 或脚本目录要保持相对路径一致否则 main.py 调用joblib.load(Model.pkl)会直接抛 FileNotFoundError。常见做法是把 Model.pkl 放在项目根目录所有脚本用绝对路径或基于__file__的路径来定位。3.3 时间序列切分与评估别把“数据泄露”写进大作业很多初学者在评估模型时直接用train_test_split(X, y, random_state42)随机切分这对普通分类任务没问题但用在天气时间序列上会形成数据泄露。原因是随机切分会把未来的样本放进训练集让模型“提前看到”后面几天的真实值导致验证分数虚高。答辩时老师只要问一句“你的测试集是随机切分的吗”就很容易露馅。正确做法是按时间顺序切分使用前 80% 训练后 20% 验证# 时间序列必须按时间顺序切分不能用随机切分 split int(len(X) * 0.8) X_train, X_test X.iloc[:split], X.iloc[split:] y_train, y_test y.iloc[:split], y.iloc[split:] model.fit(X_train, y_train) y_pred model.predict(X_test)然后计算回归指标。对天气预测来说MAE 和 RMSE 是高频使用的两个指标它们的含义不同MAE 反映平均误差单位就是摄氏度直观RMSE 因为对误差取平方后再开根所以对大误差更敏感。如果 RMSE 明显大于 MAE说明存在个别日子的预测偏差特别大常见于强降温和台风天气。指标计算方法关注点MAEmean(abs(y_true - y_pred))平均偏了多少度解释直观RMSEsqrt(mean((y_true - y_pred)^2))对少数大误差更敏感R^21 - SS_res/SS_tot模型解释了多少比例的目标方差计算这两个指标只需要两行代码from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(y_test, y_pred) rmse mean_squared_error(y_test, y_pred, squaredFalse) print(fMAE: {mae:.2f}, RMSE: {rmse:.2f})参数说明squaredFalse是 sklearn 1.x 之后推荐的写法直接返回 RMSE而不是 MSE如果使用旧版 sklearnsquaredFalse可能不兼容需要写np.sqrt(mean_squared_error(...))。顺带提一句很多示例代码打印 R^2 时会把model.score(X_test, y_test)当作准确率来宣传这不太严谨R^2 不是准确率它只能反映模型对方差解释的百分比。4. main.py 与可视化从预测结果到一张“天气网”4.1 main.py 的职责加载模型、生成数据、渲染页面main.py 是整个项目的入口它做的事并不神秘加载 Model.pkl读取待预测的数据调用模型拿到预测结果再把结果传给网页展示。这个结构对应了课程设计里“后端计算”和“前端可视化”的天然分界。在这个项目里最合适的 Web 框架是 Flask。Flask 比 Django 轻只需要一个路由就能把页面跑起来代码量少适合大作业演示。main.py 的流程大致如下from flask import Flask, render_template, jsonify import joblib import pandas as pd app Flask(__name__) model joblib.load(Model.pkl) app.route(/) def index(): # 复用 ProcessData 的清洗函数生成待预测特征 df prepare_latest_data() feature_cols [month, day, weekday, humidity, pressure, weather_code, temp_max_lag1] pred model.predict(df[feature_cols]) # 把日期和预测温度传入模板 return render_template( weather.html, datesdf[date].astype(str).tolist(), pred_tempspred.tolist() ) if __name__ __main__: app.run(debugFalse, port8080)参数说明render_template第一个参数是 HTML 模板路径后续参数是模板里需要插入的数据.astype(str)用于把日期转成字符串避免 JSON 序列化时出现Timestamp类型错误debugFalse是为了防止演示时页面报错后直接暴露交互式调试器改成 True 的话会带来安全隐患。prepare_latest_data这个函数在项目里可能没有单独命名但它的作用一定是从 date_test.csv 或实时接口构造特征逻辑上等同于把 ProcessData.py 的处理流程封装成可调用函数。4.2 ECharts 展示折线图、柱状图和全国概览怎么选天气网.html 里最值得看的是前端如何接收预测数据。ECharts 是当前天气可视化的常用库支持折线图、柱状图、地图等图表类型。对于未来几天的温度预测折线图最直观对“晴雨分布”或“风级分布”柱状图更容易比较如果想展示 china_today.csv 的全国信息用 ECharts 地图加散点图更合适。下面是一个最简的折线图模板片段注意数据和后端变量如何衔接!DOCTYPE html html head meta charsetutf-8 title天气预测可视化/title script srcstatic/echarts.min.js/script /head body div idtempChart stylewidth:100%;height:400px;/div script var chart echarts.init(document.getElementById(tempChart)); chart.setOption({ title: { text: 未来几天气温预测 }, tooltip: { trigger: axis }, xAxis: { type: category, data: {{ dates | tojson }} }, yAxis: { type: value, name: 温度(℃) }, series: [{ type: line, data: {{ pred_temps | tojson }}, smooth: true }] }); /script /body /html这里{{ dates | tojson }}是 Jinja2 模板引擎提供的过滤器作用是把 Python 列表转成合法的 JavaScript 数组而不是直接输出 Python 的方括号和单引号。smooth: true只是画曲线时让折线圆润不会改变预测数值。要注意的是如果weather.html被单独双击打开script srcstatic/echarts.min.js这种依赖路径是无法解析的必须通过 Flask 启动后再访问。如果打开页面后只有坐标轴没有线优先打开浏览器开发者工具看 Console 面板是否报echarts is not defined或data is not defined。前者说明 ECharts 脚本没加载后者说明后端没有正确传入变量。4.3 启动与联调本机跑通的最小步骤这个项目拿到手后不能直接双击 HTML 就开始完整启动顺序是确认当前 Python 环境已经安装 pandas、scikit-learn、flask、matplotlib 这些依赖建议用pip install -r requirements.txt统一安装如果没有 requirements.txt 就逐个安装。先运行一次 GetModel.py确保 Model.pkl 存在。main.py 启动时会加载这个文件如果缺失会直接报错。再运行python main.py终端会显示 Flask 服务地址一般是http://127.0.0.1:8080复制到浏览器打开。页面加载后如果图表没有显示检查是否还在命令行终端运行程序关闭终端服务会立刻停止。如果想改端口打开app.run(port8080)这一行改成比如 5000注意避免和系统其他服务冲突。这里特别提醒一点第一次运行最好在命令行用python main.py启动而不是在 IDE 里直接点运行按钮。因为有些集成开发环境的工作目录和项目根目录不同会导致相对路径找不到 date_train.csv 和 Model.pkl。如果你在用 vscode 做 Python 环境配置请把项目文件夹直接作为 vscode 根目录打开并在.vscode/settings.json中设置python.terminal.executeInFileDir: true能从根上避免这类路径问题。5. 进阶实操从“能跑”到“答辩能讲”5.1 多城市预测china_today.csv 之外的扩展方式china_today.csv 当前只保存了全国今日天气快照它本身没有历史过程不能直接用来训练多城市模型。要扩展成“预测多个城市”需要在 GetData.py 里循环多个城市 ID分别抓取历史数据后合并到 date_train.csv。城市 ID 通常和爬虫 URL 中的参数一致不能随便填数字。合并后需要给每个样本增加一列 city 并做编码处理否则模型会把不同城市的差异完全忽略。这种扩展方式会让特征列变多但也能让模型学到“同纬度城市温度相近”这样的跨城市规律。# 假设抓取逻辑按城市ID落盘合并时保留city列 all_frames [] for city_id in [101010100, 101020100]: df pd.read_csv(f{city_id}_train.csv) df[city] city_id all_frames.append(df) df_all pd.concat(all_frames, ignore_indexTrue)需要注意pd.concat时ignore_indexTrue要带上否则合并后的 DataFrame 会保留每个文件的原始索引之后做sort_values(date)时排序范围会受影响。城市 ID 在模型里应该作为特征参与训练预测阶段也要提供同一列否则 predict 会报特征数量不一致的错误。5.2 增量更新与自动重训给演示数据续命课程设计交完以后仓库里的 date_train.csv 是静态的。如果项目被放到服务器上持续演示过两周预测就会偏离现实。常见做法是写一个增量刷新脚本在 main.py 启动前检查 CSV 修改时间超过一定天数就重新执行 GetData.py 和 GetModel.py这样能保证演示数据不是陈旧数据。import os import time # 超过3天没更新就自动抓取并重训 csv_path date_train.csv last_update os.path.getmtime(csv_path) if time.time() - last_update 86400 * 3: os.system(python GetData.py) os.system(python GetModel.py)os.path.getmtime返回文件最后修改时间的时间戳time.time()是当前时间戳两数之差单位是秒。86400是一天的秒数乘 3 就是三天。这个逻辑放在 main.py 初始化里相当于给项目增加了一个轻量级自愈机制。不过要注意爬虫抓取频次不能太高否则很容易被封 IP建议只在数据明显过期时才触发。5.3 用预测对比真值图收尾答辩时最有力的一张图不是未来预测的曲线而是用 date_test.csv 里的真实观测值和模型预测值画在同一个坐标系中。这张图可以直接验证模型不是“乱猜”也能让评委一眼看出模型在哪些时段偏差大。matplotlib 画图时可以保存成 PNG并在 HTML 页面中用 img 标签展示import matplotlib.pyplot as plt plt.figure(figsize(10, 4)) plt.plot(y_test.values, labelreal) plt.plot(y_pred, labelpredicted) plt.legend() plt.title(Prediction vs Real Temperature) plt.savefig(static/validate.png, dpi150)dpi150是清晰度参数用于保证图片在 PPT 投影或 PDF 报告里不糊figsize(10, 4)控制画布宽高比适合页面内嵌展示。绘制前必须把y_test和y_pred的索引对齐最稳妥的做法是plt.plot(y_test.reset_index(dropTrue), labelreal)和plt.plot(pd.Series(y_pred), labelpredicted)避免因索引错位画出两条不相关的线。这张对比图放进去后整个项目的结果落点就从“代码跑通”变成了“预测效果可视化”。本文还有配套的精品资源点击获取