简介:这份天气预报毕业设计源码包面向计算机相关专业的毕业生与自学者,提供一套可直接运行、便于二次开发的完整项目参考,帮助解决从气象数据获取到界面展示的全流程实现问题。压缩包共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 python=3.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 用于调 API;matplotlib 做基础图表;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, params=params, timeout=10) 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 = f"weather_{datetime.now().strftime('%Y%m%d_%H%M')}.json" with open(filename, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) print(f"已保存 {len(result)} 条记录到 {filename}")逻辑说明:fetch_weather接收城市名,拼参数请求 API,把返回的 list 里每条记录精简成五个字段。raise_for_status是关键,API 返回 401 或 429 时直接报错,不会静默失败。保存时用时间戳命名,方便做增量抓取。
参数说明:units=metric表示摄氏度,lang=zh_cn返回中文天气描述。timeout=10防止网络卡死。如果你要抓多个城市,把城市列表循环调用即可,但注意免费额度,别短时间高频请求。
这个脚本跑通后,你就有了原始数据。下一步是清洗和特征工程。
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(method="linear", limit=3) # 重采样到小时级,取均值 df_hourly = df.resample("1h").mean() df_hourly[temp_col] = df_hourly[temp_col].interpolate(method="linear") return df_hourly.dropna() # 假设 df 是从 JSON 读入的 DataFrame # df = pd.read_json("weather_20240101_1200.json") # cleaned = clean_weather_data(df) # print(cleaned.head())逻辑说明:先转时间类型并排序,保证时间轴正确。IQR 检测把极端值替换为中位数,避免拉偏统计量。插值限制limit=3是防止连续缺失太多时插出虚假数据。重采样到小时级后再次插值,保证输出没有空值。
参数说明:resample("1h")可按需改成"3h"或"1D"。limit=3表示最多连续填三个点,超过就保留 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_ratio=0.7, val_ratio=0.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°C,LSTM 只降到 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_col="temp"): """训练线性回归基线模型""" 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_col="temp"): """训练随机森林和 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_estimators=200, # 树的数量 max_depth=12, # 最大深度,防止过拟合 min_samples_leaf=3, # 叶节点最少样本 random_state=42, n_jobs=-1 ), "XGBoost": XGBRegressor( n_estimators=300, learning_rate=0.05, # 学习率,越小越稳但越慢 max_depth=6, subsample=0.8, # 行采样比例 colsample_bytree=0.8, # 列采样比例 random_state=42 ) } 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_dim=64, num_layers=2, output_dim=1): super().__init__() self.lstm = nn.LSTM( input_size=input_dim, hidden_size=hidden_dim, num_layers=num_layers, batch_first=True, dropout=0.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_len=24): """把数据切成序列样本""" xs, ys = [], [] for i in range(len(data) - seq_len): xs.append(data[i:i+seq_len]) ys.append(target[i+seq_len]) return np.array(xs), np.array(ys) # 示例:假设 features_array 和 target_array 已准备好 # X, y = create_sequences(features_array, target_array, seq_len=24) # X_tensor = torch.tensor(X, dtype=torch.float32) # y_tensor = torch.tensor(y, dtype=torch.float32).unsqueeze(1) # dataset = TensorDataset(X_tensor, y_tensor) # loader = DataLoader(dataset, batch_size=32, shuffle=True)逻辑说明:LSTM 层提取时序依赖,batch_first=True让输入维度是(batch, seq, feature)。取最后一个时间步的输出接全连接层做回归。create_sequences把连续数据切成固定长度的窗口,预测下一个时间点。
参数说明:seq_len=24表示用过去 24 小时预测下一小时,可按数据粒度调整。hidden_dim=64和num_layers=2是中小规模数据的常用配置。dropout=0.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(host="0.0.0.0", port=5000, debug=False)逻辑说明:joblib.load加载训练好的模型和标准化器。接口接收特征数组,标准化后预测,返回温度值。debug=False避免答辩时暴露调试信息。
参数说明:host="0.0.0.0"允许局域网访问,方便用手机或另一台电脑演示。端口 5000 是 Flask 默认,冲突就改。生产环境应该用 gunicorn,但毕设演示用自带服务器够了。
6.2 前端图表选型与数据绑定
前端展示用 ECharts 或 Chart.js 都行。ECharts 对中文支持好,图表类型多,适合做温度曲线、湿度热力图、风向玫瑰图。数据绑定用 fetch 调后端接口,拿到 JSON 后更新图表。
关键点:答辩现场网络可能不稳定,提前把预测结果存成静态 JSON,前端优先读本地文件,接口作为备选。图表加 loading 状态,避免白屏。
6.3 打包与演示环境准备
最后一步是打包。把后端、前端、模型文件、静态数据整理到一个文件夹,写一个start.sh或start.bat一键启动。答辩前在目标机器上完整跑一遍,确认端口没被占用、依赖都装了、数据路径正确。
我一般会准备两个版本:一个在线版调接口,一个离线版读静态数据。答辩时先演示在线版,如果网络出问题立刻切离线版。这个习惯帮我省过好几次尴尬。
希望帮到你。
本文还有配套的精品资源,点击获取