简介这份天气预报毕业设计源码包面向计算机相关专业的毕业生与自学者提供一套可直接运行、便于二次开发的完整项目参考帮助解决从气象数据获取到界面展示的全流程实现问题。压缩包共123个文件约6.18MB以62个png界面素材、29个class编译文件、11个java源码、9个xml布局及apk安装包为主另含数据库、配置与资源索引文件覆盖Android客户端与后端逻辑。项目涉及数据获取与清洗、统计分析、GIS地图可视化、前后端接口设计、数据库存储及定时更新等关键环节并包含移动端界面与组件实现适合作为课程设计或毕业设计的实践案例。目前已有654人学习下载读者可从中获得完整工程结构、可运行示例与模块化代码组织思路便于对照理解天气预报系统的构建流程并快速搭建自己的版本。1. 天气预报毕业设计从数据抓取到可视化大屏的完整落地路径做天气预报毕业设计最怕的不是技术难而是选题太泛。很多同学一上来就说“我要做一个天气预报系统”结果做到一半发现数据从哪来预测模型用什么界面怎么展示三个问题卡住两个。我带过几届学生的毕设也帮某实验室做过气象数据可视化的小工具血泪经验是——天气预报毕业设计能不能顺利交付80% 取决于你第一天有没有把“数据源、预测方法、展示形式”这三件事定死。这篇笔记面向正在做天气预报毕业设计的本科生和刚入行的开发者把从数据获取、预处理、预测建模到可视化大屏的完整链路拆开讲。不堆概念每一步都给可复现的命令和参数。读完你应该能判断自己的选题该走统计方法还是机器学习数据用哪套界面用什么框架以及哪些坑现在绕开还来得及。2. 数据源选型与本地环境搭建别等跑模型才发现数据对不上2.1 免费气象数据源的对比与选择逻辑天气预报毕业设计的第一步不是写代码是确定数据从哪来。常见做法有三类公开 API、历史数据集、自建传感器。自建传感器成本高且数据质量不稳定除非你的课题是“物联网气象站”否则不建议。重点看前两类。公开 API 里和风天气、OpenWeatherMap、WeatherAPI 是学生项目里出现频率最高的。它们都提供免费额度但限制不同。和风天气免费版每天有调用次数上限适合做实时展示OpenWeatherMap 的 One Call API 可以一次拿到当前、分钟级、小时级、日级预报适合做多维度展示WeatherAPI 的历史数据接口对免费用户开放有限天数做回溯验证会受限。历史数据集方面某高校实验室常用的有 NOAA 的 GSOD全球日值摘要和 ERA5 再分析数据。GSOD 是 CSV 格式字段少、体积小适合做入门级时间序列预测ERA5 是 NetCDF 格式变量多、精度高但需要额外装 xarray 和 netCDF4 才能读对新手不友好。选型建议如果你的毕设重点是“预测算法对比”用 GSOD 历史数据本地跑不依赖网络如果重点是“实时天气展示系统”用和风天气或 OpenWeatherMap 的 API配合定时任务抓取。两者结合也可以——历史数据训练模型API 数据做实时推理输入。注意免费 API 的密钥不要硬编码在代码里用环境变量或配置文件读取否则提交代码时容易泄露。2.2 用 Python 搭建可复现的开发环境确定数据源后先把环境搭好。我一般用 conda 建独立环境避免和系统 Python 冲突。以下命令在 Windows 和 macOS 上都能跑。# 创建名为 weather-design 的虚拟环境指定 Python 3.10 conda create -n weather-design python3.10 -y # 激活环境 conda activate weather-design # 安装核心依赖数据处理、请求、可视化、机器学习 pip install pandas numpy requests matplotlib scikit-learn xarray netCDF4 flask # 如果要用深度学习做时序预测再加装 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu逻辑说明pandas 和 numpy 负责表格与数组运算requests 用于调 APImatplotlib 做基础图表scikit-learn 提供回归和评估指标xarray 和 netCDF4 读 ERA5 数据flask 用于后续搭展示接口。torch 用 CPU 版即可毕设规模不需要 GPU。参数说明Python 3.10 是当前库兼容性较好的版本3.11 以上部分气象库可能还没适配。conda 环境名自己取但建议和项目名一致方便管理。如果 pip 安装 torch 太慢可以换清华源但不要用来源不明的镜像。环境搭好后用python -c import pandas; print(pandas.__version__)验证。能打印出版本号就说明基础环境没问题。2.3 从 API 拉取实时天气数据的最小脚本以 OpenWeatherMap 为例注册后拿到 API Key写一个最小抓取脚本。这个脚本只做一件事给定城市拿当前天气和未来几小时预报存成 JSON。import os import requests import json from datetime import datetime # 从环境变量读取 API Key避免硬编码 API_KEY os.environ.get(OWM_API_KEY) BASE_URL https://api.openweathermap.org/data/2.5/forecast def fetch_weather(city: str, country_code: str CN): 拉取指定城市的 5 天 / 3 小时预报数据 params { q: f{city},{country_code}, appid: API_KEY, units: metric, # 摄氏度 lang: zh_cn # 中文描述 } resp requests.get(BASE_URL, paramsparams, timeout10) resp.raise_for_status() # 非 200 直接抛异常 data resp.json() # 只保留需要的字段减小存储体积 records [] for item in data[list]: records.append({ time: item[dt_txt], temp: item[main][temp], humidity: item[main][humidity], weather: item[weather][0][description], wind_speed: item[wind][speed] }) return records if __name__ __main__: result fetch_weather(Beijing) # 按时间戳命名避免覆盖 filename fweather_{datetime.now().strftime(%Y%m%d_%H%M)}.json with open(filename, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2) print(f已保存 {len(result)} 条记录到 {filename})逻辑说明fetch_weather接收城市名拼参数请求 API把返回的 list 里每条记录精简成五个字段。raise_for_status是关键API 返回 401 或 429 时直接报错不会静默失败。保存时用时间戳命名方便做增量抓取。参数说明unitsmetric表示摄氏度langzh_cn返回中文天气描述。timeout10防止网络卡死。如果你要抓多个城市把城市列表循环调用即可但注意免费额度别短时间高频请求。这个脚本跑通后你就有了原始数据。下一步是清洗和特征工程。3. 数据预处理与特征工程把原始气象记录变成模型能吃的格式3.1 缺失值、异常值与时间对齐的处理原始数据几乎不可能直接喂给模型。常见问题有三个缺失值、异常值、时间粒度不统一。GSOD 数据里某些站点某天缺测是常态API 数据里偶尔会返回空字段。异常值更隐蔽——比如温度出现 999 或 -999那是传感器故障码不是真实气温。处理缺失值我一般先看缺失比例。低于 5% 的用前后值线性插值高于 20% 的直接丢弃该时间段不要硬填。异常值用 IQR 方法检测计算第一四分位数 Q1 和第三四分位数 Q3超出Q1 - 1.5*IQR或Q3 1.5*IQR范围的值标记为异常然后用中位数替换。时间对齐是容易被忽略的一步。API 返回的是 3 小时粒度GSOD 是日粒度如果你要融合两个数据源必须统一到同一时间频率。常见做法是重采样到小时级用pandas.resample配合插值。import pandas as pd import numpy as np def clean_weather_data(df: pd.DataFrame, time_col: str time, temp_col: str temp): 清洗气象数据时间解析、异常值处理、缺失值插值 df df.copy() df[time_col] pd.to_datetime(df[time_col]) df df.sort_values(time_col).set_index(time_col) # 异常值用 IQR 检测并替换为中位数 Q1 df[temp_col].quantile(0.25) Q3 df[temp_col].quantile(0.75) IQR Q3 - Q1 lower Q1 - 1.5 * IQR upper Q3 1.5 * IQR median_temp df[temp_col].median() df.loc[(df[temp_col] lower) | (df[temp_col] upper), temp_col] median_temp # 缺失值线性插值最多连续填 3 个 df[temp_col] df[temp_col].interpolate(methodlinear, limit3) # 重采样到小时级取均值 df_hourly df.resample(1h).mean() df_hourly[temp_col] df_hourly[temp_col].interpolate(methodlinear) return df_hourly.dropna() # 假设 df 是从 JSON 读入的 DataFrame # df pd.read_json(weather_20240101_1200.json) # cleaned clean_weather_data(df) # print(cleaned.head())逻辑说明先转时间类型并排序保证时间轴正确。IQR 检测把极端值替换为中位数避免拉偏统计量。插值限制limit3是防止连续缺失太多时插出虚假数据。重采样到小时级后再次插值保证输出没有空值。参数说明resample(1h)可按需改成3h或1D。limit3表示最多连续填三个点超过就保留 NaN后续dropna丢弃。如果你的数据缺失严重不要强行插值考虑换数据源或缩短研究时段。3.2 构造时序特征滑动窗口与滞后项天气预报本质是时间序列问题。把原始温度、湿度直接丢给模型效果通常一般因为模型看不到“趋势”。构造滑动窗口特征是提升效果最直接的手段。常见特征包括过去 1 小时、3 小时、6 小时、12 小时、24 小时的均值和标准差温度的变化率当前减前一小时昼夜标志小时数是否在 6 到 18 之间。这些特征能让线性回归和树模型都明显受益。def build_features(df: pd.DataFrame, target_col: str temp): 构造滑动窗口和滞后特征 result df.copy() # 滞后特征过去 1、3、6、12、24 小时的值 for lag in [1, 3, 6, 12, 24]: result[f{target_col}_lag_{lag}] result[target_col].shift(lag) # 滑动统计过去 6 小时和 24 小时的均值、标准差 for window in [6, 24]: result[f{target_col}_mean_{window}] result[target_col].rolling(window).mean() result[f{target_col}_std_{window}] result[target_col].rolling(window).std() # 变化率 result[f{target_col}_diff_1] result[target_col].diff(1) # 昼夜标志 result[is_daytime] result.index.hour.apply(lambda h: 1 if 6 h 18 else 0) # 丢弃因 shift 和 rolling 产生的空值 result result.dropna() return result # features build_features(cleaned) # print(features.columns.tolist())逻辑说明shift(lag)把过去的值挪到当前行让模型能“看到”历史。rolling(window).mean()计算滑动均值捕捉短期趋势。diff(1)是一阶差分表示变化速度。昼夜标志是领域知识温度有明显的日周期。参数说明滞后阶数根据你的数据粒度调整。小时级数据用 1、3、6、12、24 合理日级数据用 1、3、7、14、30。窗口大小同理。特征不是越多越好构造完后用相关性矩阵筛一遍避免多重共线性。3.3 训练集、验证集、测试集的划分原则时间序列不能随机划分否则会用未来数据预测过去造成数据泄露。正确做法是按时间顺序切分前 70% 训练中间 15% 验证最后 15% 测试。如果数据量少可以用滚动预测的方式做交叉验证。def time_split(df: pd.DataFrame, train_ratio0.7, val_ratio0.15): 按时间顺序划分训练、验证、测试集 n len(df) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) train df.iloc[:train_end] val df.iloc[train_end:val_end] test df.iloc[val_end:] return train, val, test # train, val, test time_split(features) # print(f训练集 {len(train)} 条验证集 {len(val)} 条测试集 {len(test)} 条)逻辑说明iloc按位置切片保证时间顺序不乱。训练集用于拟合验证集用于调参和早停测试集只在最后评估一次。不要反复用测试集调参否则测试结果会偏乐观。参数说明比例可以按数据量调整。如果总样本少于 1000 条建议 80/10/10如果超过 10000 条70/15/15 足够。验证集的作用是选模型和超参数测试集是最终交付指标。4. 预测模型选型与训练从线性回归到 LSTM 的取舍4.1 基线模型线性回归和 ARIMA 够不够用很多同学一上来就想上 LSTM觉得深度学习才“高级”。但毕设答辩时老师更看重你能否解释清楚模型为什么有效。线性回归和 ARIMA 虽然简单却是最好的基线。如果线性回归在测试集上的 RMSE 是 2.5°CLSTM 只降到 2.3°C那多出来的复杂度不值得。线性回归适合特征工程做得充分的情况。把上一节的滞后特征和滑动统计丢进去用LinearRegression拟合通常能拿到一个不算差的基线。ARIMA 更适合没有外生变量的纯时序数据用statsmodels几行就能跑。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np def train_linear(train, val, feature_cols, target_coltemp): 训练线性回归基线模型 X_train train[feature_cols] y_train train[target_col] X_val val[feature_cols] y_val val[target_col] model LinearRegression() model.fit(X_train, y_train) pred model.predict(X_val) mae mean_absolute_error(y_val, pred) rmse np.sqrt(mean_squared_error(y_val, pred)) print(f验证集 MAE: {mae:.2f}°C, RMSE: {rmse:.2f}°C) return model # feature_cols [c for c in train.columns if c ! temp] # model train_linear(train, val, feature_cols)逻辑说明LinearRegression直接拟合特征到温度的线性关系。MAE 和 RMSE 是回归任务的标准指标MAE 反映平均误差RMSE 对大误差更敏感。打印出来和后续模型对比。参数说明feature_cols要排除目标列本身否则会泄露。如果特征里有高度相关的列线性回归的系数会不稳定可以先做方差膨胀因子筛选。4.2 用随机森林和 XGBoost 提升非线性拟合能力线性回归假设特征和温度是线性关系但气象数据明显非线性。随机森林和 XGBoost 能自动捕捉交互项和非线性通常比线性回归好一截而且训练快、调参少非常适合毕设。from sklearn.ensemble import RandomForestRegressor from xgboost import XGBRegressor def train_tree_models(train, val, feature_cols, target_coltemp): 训练随机森林和 XGBoost对比验证集指标 X_train, y_train train[feature_cols], train[target_col] X_val, y_val val[feature_cols], val[target_col] models { RandomForest: RandomForestRegressor( n_estimators200, # 树的数量 max_depth12, # 最大深度防止过拟合 min_samples_leaf3, # 叶节点最少样本 random_state42, n_jobs-1 ), XGBoost: XGBRegressor( n_estimators300, learning_rate0.05, # 学习率越小越稳但越慢 max_depth6, subsample0.8, # 行采样比例 colsample_bytree0.8, # 列采样比例 random_state42 ) } for name, model in models.items(): model.fit(X_train, y_train) pred model.predict(X_val) mae mean_absolute_error(y_val, pred) rmse np.sqrt(mean_squared_error(y_val, pred)) print(f{name} 验证集 MAE: {mae:.2f}°C, RMSE: {rmse:.2f}°C) return models # models train_tree_models(train, val, feature_cols)逻辑说明随机森林通过多棵树投票降低方差XGBoost 通过梯度提升降低偏差。两者都对特征尺度不敏感不需要标准化。n_jobs-1用满 CPU 核心加速。参数说明n_estimators越大越稳但越慢200 到 500 之间通常够用。max_depth控制复杂度太深会过拟合。learning_rate和n_estimators要配合调学习率小就增大树数。subsample和colsample_bytree是防过拟合的常用手段。4.3 LSTM 时序模型的搭建与训练要点如果树模型的效果还不够或者你的毕设明确要求深度学习可以上 LSTM。但要注意LSTM 对数据量和调参更敏感小数据集上不一定比 XGBoost 好。搭建时用 PyTorch输入形状是(batch, sequence_length, features)。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class WeatherLSTM(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers2, output_dim1): super().__init__() self.lstm nn.LSTM( input_sizeinput_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue, dropout0.2 ) self.fc nn.Linear(hidden_dim, output_dim) def forward(self, x): # x: (batch, seq_len, input_dim) out, _ self.lstm(x) # 取最后一个时间步的输出 out out[:, -1, :] return self.fc(out) def create_sequences(data, target, seq_len24): 把数据切成序列样本 xs, ys [], [] for i in range(len(data) - seq_len): xs.append(data[i:iseq_len]) ys.append(target[iseq_len]) return np.array(xs), np.array(ys) # 示例假设 features_array 和 target_array 已准备好 # X, y create_sequences(features_array, target_array, seq_len24) # X_tensor torch.tensor(X, dtypetorch.float32) # y_tensor torch.tensor(y, dtypetorch.float32).unsqueeze(1) # dataset TensorDataset(X_tensor, y_tensor) # loader DataLoader(dataset, batch_size32, shuffleTrue)逻辑说明LSTM 层提取时序依赖batch_firstTrue让输入维度是(batch, seq, feature)。取最后一个时间步的输出接全连接层做回归。create_sequences把连续数据切成固定长度的窗口预测下一个时间点。参数说明seq_len24表示用过去 24 小时预测下一小时可按数据粒度调整。hidden_dim64和num_layers2是中小规模数据的常用配置。dropout0.2防过拟合。训练时用 MSE 损失和 Adam 优化器学习率从 1e-3 开始试。训练循环里记得加验证集早停如果验证损失连续 5 个 epoch 不下降就停止训练保存验证损失最低的模型。这样能避免过拟合也省时间。5. 避坑与常见问题那些让毕设卡住的真实原因5.1 API 调用超限或返回空数据现象脚本跑着跑着报 429 错误或者返回的 JSON 里list字段为空。原因免费 API 有每分钟和每天调用上限短时间高频请求会触发限流。空数据通常是城市名拼写错误或 API Key 无效。解决在请求之间加time.sleep(1)把抓取频率降到每分钟 10 次以下。城市名用英文或 API 文档指定的格式。Key 失效时先检查环境变量是否设置正确再确认账号是否欠费。5.2 时间序列数据泄露导致指标虚高现象测试集 RMSE 低到 0.5°C但实际预测下一周天气时误差巨大。原因划分数据时用了随机切分或者构造特征时用了未来信息。比如用全量数据的均值做标准化就泄露了测试集信息。解决严格按时间顺序切分。标准化参数只用训练集计算再应用到验证集和测试集。滑动窗口特征只允许用当前时刻及之前的数据。5.3 LSTM 训练损失不下降或震荡现象训练几个 epoch 后损失卡住不动或者上下剧烈跳动。原因学习率太大、序列长度不合适、数据没归一化、batch size 太小。解决先把特征做 MinMax 归一化到 [0,1]。学习率从 1e-3 降到 1e-4 试。序列长度从 24 改成 12 或 48 对比。batch size 调到 64 或 128。如果还不行检查输入数据里有没有 NaN。5.4 可视化大屏在答辩现场加载失败现象本地跑得好好的答辩时打开页面一片空白或图表不显示。原因前端请求后端接口跨域被拦或者后端服务没启动或者数据文件路径写成了绝对路径。解决Flask 后端加 CORS 头或者用 Nginx 反代。答辩前把数据预加载到本地 JSON前端直接读静态文件不依赖实时接口。路径全部用相对路径别写C:\Users\...。5.5 模型文件太大无法提交或加载慢现象LSTM 模型保存后几百 MB提交系统限制 100 MB或者加载要等几分钟。原因保存了完整优化器状态和计算图或者模型本身参数太多。解决只保存state_dict加载时重建模型结构再load_state_dict。如果还大减小hidden_dim和num_layers。树模型用joblib压缩保存通常只有几 MB。6. 可视化大屏与部署让毕设成果能演示、能交互6.1 用 Flask 提供预测接口模型训练完后需要一个后端把预测能力暴露给前端。Flask 足够轻量适合毕设。核心思路启动时加载模型和数据提供一个/predict接口接收城市或时间参数返回 JSON。from flask import Flask, request, jsonify import joblib import numpy as np app Flask(__name__) # 启动时加载模型和标准化参数 model joblib.load(xgb_weather_model.pkl) scaler joblib.load(scaler.pkl) app.route(/predict, methods[POST]) def predict(): data request.get_json() features np.array(data[features]).reshape(1, -1) features_scaled scaler.transform(features) pred model.predict(features_scaled)[0] return jsonify({temperature: round(float(pred), 2)}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)逻辑说明joblib.load加载训练好的模型和标准化器。接口接收特征数组标准化后预测返回温度值。debugFalse避免答辩时暴露调试信息。参数说明host0.0.0.0允许局域网访问方便用手机或另一台电脑演示。端口 5000 是 Flask 默认冲突就改。生产环境应该用 gunicorn但毕设演示用自带服务器够了。6.2 前端图表选型与数据绑定前端展示用 ECharts 或 Chart.js 都行。ECharts 对中文支持好图表类型多适合做温度曲线、湿度热力图、风向玫瑰图。数据绑定用 fetch 调后端接口拿到 JSON 后更新图表。关键点答辩现场网络可能不稳定提前把预测结果存成静态 JSON前端优先读本地文件接口作为备选。图表加 loading 状态避免白屏。6.3 打包与演示环境准备最后一步是打包。把后端、前端、模型文件、静态数据整理到一个文件夹写一个start.sh或start.bat一键启动。答辩前在目标机器上完整跑一遍确认端口没被占用、依赖都装了、数据路径正确。我一般会准备两个版本一个在线版调接口一个离线版读静态数据。答辩时先演示在线版如果网络出问题立刻切离线版。这个习惯帮我省过好几次尴尬。希望帮到你。本文还有配套的精品资源点击获取