☰
Python天气预测与可视化实战:从数据管道到论文配图
2026/9/28 5:26:32 网站建设 项目流程

简介:基于Python的天气预测与天气可视化毕业设计项目,提供完整源码与文档说明,适合计算机相关专业学生用于毕业设计、课程设计或期末大作业,也适合想实战Python数据分析和机器学习的初学者。项目覆盖天气数据爬取、历史数据探索、气温预测模型训练与GUI可视化,其中气温预测部分包含线性回归、决策树、随机森林、3层MLP及LSTM等多种算法,并附带训练好的h5/pkl/joblib模型文件和归一化参数,方便直接加载使用。资源共38个文件,以Python脚本、ipynb笔记、模型文件、图片文档和CSV数据集为主,压缩包整体约12.17MB,内含项目手册、依赖清单、全国天气数据、历史天气CSV、GUI正式版与最简版脚本,目录划分清晰,便于按模块学习。目前已有99人学习下载,代码完整可运行,适合作为高分毕业设计的参考模板,也可在此基础上快速扩展为个人项目。

1. 基于Python的天气预测与天气可视化项目,真正难的不是模型

“基于Python的天气预测和天气可视化项目”这个名字,第一眼容易让“预测”两个字带偏,以为工程量全砸在模型上。真正上手后你会发现,天气预测是最典型的数据管道占比远大于模型的任务:数据要拿得到、清洗得干净、时间特征造得出,模型反而可以用很成熟的回归思路;而天气可视化是另一个容易被低估的环节,论文里要插趋势图,答辩时要展示交互地图,图的风格直接决定整个项目看起来像不像高分毕业设计。

这篇笔记把整条链路拆成五段来讲:数据获取、特征构造、模型选型、可视化落地、以及最常翻车的几个坑。后面每一步都给出能直接运行的 Python 代码与参数说明,适合正在做毕业设计、需要一套“源码+文档说明”完整交付物的学生,也适合学完 Pandas 和 Scikit-learn 但缺一两个完整项目经验的人照着复现。

2. 天气预测核心:数据获取、特征构造与模型选型

2.1 数据从哪拿:免 Key 的归档接口和爬虫两条路线,请求参数怎么设

第一步不是写模型,是先确认数据源。天气预测需要的是“连续的历史数据”,不是当天看一眼就完事。常见做法是优先选公开的归档型天气服务,不需要申请密钥,直接把经纬度和起止日期传进去,就能拿到几十年的逐日最高温、最低温、降雨量。这类接口返回的是结构化 JSON,解析成本低,非常适合毕业设计。

import requests import pandas as pd BASE_URL = "https://archive-api.open-meteo.com/v1/archive" def fetch_history(lat, lon, start_date, end_date, variables=None): if variables is None: variables = ["temperature_2m_max", "temperature_2m_min", "precipitation_sum"] params = { "latitude": lat, "longitude": lon, "start_date": start_date, "end_date": end_date, "daily": ",".join(variables), "timezone": "Asia/Shanghai", } resp = requests.get(BASE_URL, params=params, timeout=15) resp.raise_for_status() data = resp.json() daily = data["daily"] df = pd.DataFrame(daily) df["date"] = pd.to_datetime(df["time"]) df = df.set_index("date").sort_index() return df df = fetch_history(39.9042, 116.4074, "2023-01-01", "2023-12-31") print(df.head())

代码逻辑不复杂,但有几个参数值得展开。latitude和longitude是目标城市的经纬度,毕业设计一般选自己所在城市,答辩答得清楚;daily指定要哪些逐日字段,默认我把最高温、最低温和降雨量都拿下来;timezone填Asia/Shanghai,否则时间轴会对不齐,画图时横坐标会出现半天偏移。timeout设到 15 秒,校园网不稳定时避免 requests 无限阻塞,然后是resp.raise_for_status(),状态码非 200 立刻抛错,这比后面拿到一堆 HTML 乱码再排查要省时间。

如果某个城市没有归档数据,或者你想展示“自己写爬虫”这个技能点,另一条路线是用 requests 加 BeautifulSoup 抓天气网页。思路是伪装 User-Agent 请求目标页面,解析表格里的历史数据。这里有个关键提醒:爬虫遇到的第一个坎通常是验证码和请求频率限制,所以我在实际项目里总是把“已抓取原始 JSON”缓存到本地data/raw/目录,保证后续跑特征工程时不需要重新联网。数据拿到后先看一眼df.isna().sum(),很多城市数据存在零星缺失,这一点后面避坑章会细讲。

2.2 把原始天气数据变成特征:时间特征、滞后值与滑动均值

原始数据只有日期和温度,直接喂给模型也能跑,但精度上不去。天气是一个强自相关的序列:今天的最高温和昨天、前天高度相关,也存在明显的周期。所以常见做法是构造三组特征:时间特征、滞后值、滑动统计量。滞后值的意思是“前 1 天、前 2 天、前 7 天的最高温”,滑动均值解决的是“过去一周整体偏热还是偏冷”的趋势问题。

def build_features(df, target="temperature_2m_max", lags=(1, 2, 7), windows=(3, 7)): df = df.copy() df["month"] = df.index.month df["day_of_year"] = df.index.dayofyear df["weekday"] = df.index.weekday df["is_weekend"] = (df["weekday"] >= 5).astype(int) for lag in lags: df[f"lag_{lag}"] = df[target].shift(lag) for win in windows: df[f"rolling_mean_{win}"] = df[target].rolling(win).mean() df[f"rolling_std_{win}"] = df[target].rolling(win).std() df["target_next"] = df[target].shift(-1) return df feat_df = build_features(df) print(feat_df.tail())

这里有一个新手最容易踩的坑:shift(-1)生成的是“未来值”。训练模型时它的角色是标签y,不是特征。在预测阶段,明天还没发生,你不可能拿到明天的最高温当输入。所以完整流程是:先构造特征,再用target_next作为训练标签,而X只保留lag_*、rolling_*和时间特征。推理时预测第 N+1 天,输入里最高能用到第 N 天的值。窗口参数lags=(1,2,7)表示取前一天、前两天和一周前,覆盖短周期和完整周周期;windows=(3,7)则让模型知道最近三天和七天的平均温、波动幅度。构造完成后用.dropna()剔除开头那些没有滞后值的行,这部分样本本来就练不了。

2.3 模型选型:线性回归打底、随机森林提升、Prophet 看趋势

天气预测的基线模型用线性回归足够,它帮我们确认特征和标签之间确实存在线性关系,同时给出可解释的系数。想提分就往随机森林或梯度提升走,它们能捕捉温度与月份、滞后值之间的非线性交互,又不需要做特征缩放。我一般会在 main 流程里同时跑三个模型,最前面加一个“按时间顺序切分”的交叉验证,而不是用train_test_split随机切分,因为天气序列相邻两天高度相关,随机切分会把“昨天的信息漏给今天”,造成虚高的评估分数。

from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np def train_and_evaluate(feat_df, test_days=30): feat_df = feat_df.dropna() split_idx = len(feat_df) - test_days feature_cols = [c for c in feat_df.columns if c != "target_next"] X_train = feat_df.iloc[:split_idx][feature_cols] y_train = feat_df.iloc[:split_idx]["target_next"] X_test = feat_df.iloc[split_idx:][feature_cols] y_test = feat_df.iloc[split_idx:]["target_next"] models = { "linear": LinearRegression(), "random_forest": RandomForestRegressor( n_estimators=300, max_depth=6, min_samples_leaf=3, random_state=42, ), } results = {} for name, model in models.items(): model.fit(X_train, y_train) y_pred = model.predict(X_test) mae = mean_absolute_error(y_test, y_pred) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) results[name] = {"model": model, "mae": mae, "rmse": rmse} print(f"{name}: MAE={mae:.2f}°C, RMSE={rmse:.2f}°C") return results

这段代码里最值得调的是随机森林的三个参数。n_estimators设为 300 足够稳定,再多训练时间变长但收益很小;max_depth=6是刻意限制树深,避免模型把训练集日期背下来;min_samples_leaf=3要求叶子节点最少 3 个样本,对抑制过拟合作用最明显。线性回归在这里不是陪跑,它的 MAE 就是一张“下限成绩单”,如果随机森林连线性回归都打不过,先回头查特征是否构造错了。

三组特征在随机森林里的重要性排序通常也很有参考价值:lag_1和rolling_mean_7几乎总是排最前。这说明在天气预测场景里,近因数据远比月份信息有用,答辩时把model.feature_importances_画成柱状图,是一个很能说明你理解业务的小亮点。Prophet 一类的时间序列工具我也跑过,优势是自动处理节假日和趋势,但把它的预测结果和同期历史均值叠在一张图上时,肉眼往往看不出比随机森林强,而且安装包比较重,放进入门项目里性价比不高。

3. 天气可视化方案:用 matplotlib 出论文图,用 pyecharts 出交互图

3.1 先想清楚交付物:论文图用 matplotlib,演示用 pyecharts 还是 Streamlit

可视化最忌讳的是写到一半换库。我在定方案前会先问一句:最终交到老师手里的是什么?如果是 Word 论文,需要的是矢量图,matplotlib 输出 PDF 或高分辨率 PNG,放大不糊、颜色朴素,这是 pyecharts 导出图片做不到的。如果是答辩现场的网页演示,需要的是能缩放、悬停显示数值的交互图,pyecharts 的图表更合适,再配一个 Streamlit 页面把预测曲线、误差分布、城市地图全部放到一个网页里演示。

可视化工具交付形态适合场景主要成本
matplotlibPDF / PNG 图片论文插图、报告插图中文与字体配置
pyecharts独立 HTML 文件交互趋势图、城市地图地图资源依赖网络
Streamlit本地网页服务答辩现场整体演示需要运行后展示

三个工具不是互斥关系。我的做法是:matplotlib 负责产出论文需要的静态图,pyecharts 产出可以点击缩放的全国城市温度地图,Streamlit 只做傻瓜式集成入口,让评审老师双击一下就能看到整个项目。如果时间紧张只保留两个,建议保留 matplotlib 和 pyecharts,Streamlit 的内容其实只是把前两者的产物嵌进网页。

3.2 趋势图和误差曲线:matplotlib 的中文字体与画布尺寸配置

matplotlib 的默认字体不包含中文字符,这也是天气可视化项目翻车率最高的一步。解决方法是显式指定字体列表和前两个rcParams。字体列表顺序有讲究:先SimHei,系统没有就退化到Microsoft YaHei,macOS 上再退化到PingFang SC,这样换电脑运行也不会崩。

import matplotlib.pyplot as plt import matplotlib.font_manager as fm plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "PingFang SC"] plt.rcParams["axes.unicode_minus"] = False def plot_temperature_trend(test_df, model, feature_cols, output_path="trend.png"): model = model["random_forest"]["model"] y_pred = model.predict(test_df[feature_cols]) y_true = test_df["target_next"] fig, ax = plt.subplots(figsize=(12, 5), dpi=200) ax.plot(y_true.index, y_true.values, label="实际最高温", linewidth=2, color="#333333") ax.plot(y_true.index, y_pred, label="预测最高温", linewidth=1.8, color="#c0392b", linestyle="--") ax.fill_between(y_true.index, y_pred - 1.5, y_pred + 1.5, color="#c0392b", alpha=0.2, label="±1.5°C 误差带") ax.set_title("未来30天最高温预测 vs 实际", fontsize=16, pad=12) ax.set_xlabel("日期", fontsize=12) ax.set_ylabel("温度(°C)", fontsize=12) ax.legend(loc="upper left", frameon=False) ax.grid(alpha=0.3, linestyle="--") fig.autofmt_xdate() plt.tight_layout() plt.savefig(output_path, bbox_inches="tight") plt.close()

这段代码的细节集中在三个地方。figsize=(12,5)配合dpi=200,导出图片能直接放进论文,不会因为放大而出现锯齿;alpha=0.2的误差带用的是 ±1.5°C,这个数值可以按测试集的 MAE 来定,比如 MAE 是 1.8,就把误差带写成 ±1.8,让图上的灰色地带如实反映模型的平均波动;fig.autofmt_xdate()会自动旋转日期标签,否则日期重叠成一团黑。颜色选深色系,不要用纯红配纯绿,打印出来也容易区分。

3.3 城市天气热力地图:pyecharts Map 组件与全局配置项

毕业设计里做一张“全国主要城市最高温分布”地图,视觉冲击力远大于趋势图。pyecharts 的 Map 组件用法很短,真正要调的是visualmap_opts,它控制颜色渐变和数值分段。

from pyecharts import options as opts from pyecharts.charts import Map def render_city_map(city_temps, output_path="weather_map.html"): data_pairs = [[city, temp] for city, temp in city_temps.items()] c = ( Map() .add( series_name="最高温度(°C)", data_pair=data_pairs, maptype="china", is_map_symbol_show=False, ) .set_global_opts( title_opts=opts.TitleOpts(title="全国主要城市今日最高温"), visualmap_opts=opts.VisualMapOpts( min_=0, max_=40, range_color=["#2c7fb8", "#7fcdbb", "#ffffb2", "#fc8d59", "#d7301f"], is_piecewise=False, ), ) ) c.render(output_path) print("地图已输出:", output_path)

maptype="china"指定中国地图底图;is_map_symbol_show=False建议打开,否则每个城市会叠一个红色圆点,数值多时看起来非常乱。range_color用低饱和度蓝色到红色的渐变,最低温和最高温一眼可分;is_piecewise=False表示连续渐变,如果打开变成分段图例,适合展示“高温城市/低温城市”这种分类概念,论文里我常用分段模式,看起来更有分析感。这个组件在 pyecharts 2.x 版本下地图 js 文件需要联网加载,离线打开 HTML 会白屏,这个坑下面的避坑章节会给出具体解法。

4. 把爬虫、预测、可视化串成一个可交付的项目:代码结构与运行顺序

4.1 项目目录怎么组织:源码包和文档说明的常见摆放方式

“高分毕业设计”的源码包,和随便跑通的脚本最大的区别在两点:一是别人拿到后能不能按 README 三步跑起来,二是文档能不能解释每个文件为什么存在。我搭这类项目时习惯用下面的目录结构,它同时兼容“源码”和“文档说明”两部分交付物:

weather-forecast-project/ ├── README.md ├── requirements.txt ├── config.yaml ├── main.py ├── data/ │ ├── raw/ # 原始 API 响应缓存 │ └── processed/ # 特征工程后的 CSV ├── src/ │ ├── __init__.py │ ├── data_fetcher.py │ ├── features.py │ ├── train.py │ └── visualize.py ├── outputs/ │ ├── trend.png │ ├── error_hist.png │ ├── weather_map.html │ └── prediction.csv └── docs/ ├── 需求说明.md ├── 设计与实现.md └── 使用说明.md

data/raw和data/processed分开是数据管道的常见操作,原始数据不可变,处理后的数据随时可以重新生成;outputs目录放论文要引用的图片和表格;docs里不用堆字,重点写清三件事:数据从哪来、模型怎么训练、怎么复现运行结果。README 里第一段写项目定位,第二段写执行顺序,第三段写运行环境,三句话讲完就够,老师不会在一份源码包里找小说读。

4.2 从 config 到一条命令跑通:主流程的先后顺序

把所有参数集中到config.yaml,主程序只负责按顺序调用各阶段函数。这样做的好处是评审老师打开代码时,不需要在文件间跳来跳去找参数;你自己换城市、换时间范围也只改一个文件。

# config.yaml data: lat: 39.9042 lon: 116.4074 start_date: "2023-01-01" end_date: "2023-12-31" feature: target: "temperature_2m_max" lags: [1, 2, 7] windows: [3, 7] model: test_days: 30 n_estimators: 300 max_depth: 6 min_samples_leaf: 3 cities: 北京: 39.9042 上海: 31.2304 广州: 23.1291
import yaml import pandas as pd from src.data_fetcher import fetch_history from src.features import build_features from src.train import train_and_evaluate from src.visualize import plot_temperature_trend, render_city_map def load_config(path="config.yaml"): with open(path, encoding="utf-8") as f: return yaml.safe_load(f) def main(): cfg = load_config() df = fetch_history(cfg["data"]["lat"], cfg["data"]["lon"], cfg["data"]["start_date"], cfg["data"]["end_date"]) feat_df = build_features(df, target=cfg["feature"]["target"], lags=tuple(cfg["feature"]["lags"])) results = train_and_evaluate(feat_df, test_days=cfg["model"]["test_days"]) city_temps = {} for city, coords in cfg["cities"].items(): city_df = fetch_history(coords, coords + 0.001, "2024-01-01", "2024-01-31") city_temps[city] = round(city_df["temperature_2m_max"].mean(), 1) plot_temperature_trend(feat_df, results, output_path="outputs/trend.png") render_city_map(city_temps, output_path="outputs/weather_map.html") if __name__ == "__main__": main()

主流程的顺序是固定的:数据获取 → 特征构造 → 模型训练 → 趋势图 → 城市地图。任何一个环节失败,前面的产物还保留在data/和outputs/里,不用全部重跑一遍。train_and_evaluate返回的results字典里同时存了模型对象和评估指标,画趋势图时直接用模型对象做预测,不需要再训练一次。这里我故意让城市温度用简单取均值的方式计算,避免整个主流程被地图拖慢;如果想让地图更有说服力,可以给每个城市都单独跑一次随机森林预测,再把预测值传进地图,代价是运行时间变成按分钟计算。

4.3 把预测结果导出成 CSV 和图片:让论文能直接引用

论文量化部分需要实证数据支撑。预测完成之后,我总会额外输出一张prediction.csv,里面包含日期、实际值、预测值、误差四列。评审老师不一定会去看代码,但一定会看这张表里“平均误差”这个数字。

def export_prediction(test_df, model, feature_cols, output_path="outputs/prediction.csv"): y_pred = model.predict(test_df[feature_cols]) result = pd.DataFrame({ "date": test_df.index.date, "actual": test_df["target_next"].values, "predicted": y_pred, "error": y_pred - test_df["target_next"].values, }) result.to_csv(output_path, index=False, encoding="utf-8-sig") return result # 在 train_and_evaluate 内部对每个模型都调用一次 export_prediction

encoding="utf-8-sig"是专门给 Excel 用户准备的,用默认utf-8导出后,表格里的中文列名在老版本 Excel 里会乱码。误差列我写成predicted - actual,正数代表预测偏高,负数代表偏低,后面画残差图时直接拿这一列当数据源。论文截图和表格数据都从outputs/目录取,保证正文数字和代码运行结果完全一致,这是“数据可复现”最直观的体现。

5. 天气预测项目最容易踩的 5 个坑:现象、原因与解法

5.1 随机切分数据集导致时间泄漏:训练集很漂亮,验证集崩盘

现象:用train_test_split(test_size=0.2)切数据,随机森林 MAE 只有 0.8°C,换成按时间顺序切分后 MAE 变成 2.3°C,模型“突然变笨”。

原因:天气序列相邻天高度相关,随机切分时测试集里混进了训练集前后一两天的数据,模型等于偷看了“未来答案”。这属于典型的泄漏,分数虚高却没有任何实战意义。

解决:固定使用时间顺序切分,前 70% 行训练、后 30% 行验证。在train_and_evaluate中用split_idx = len(feat_df) - test_days切分,并把随机森林训练时的random_state固定住,保证每次运行结果一致,答辩演示不会出现二次运行结论不一样。

5.2 matplotlib 中文显示成方框:字体与负号的两个开关

现象:标题和图例的中文全部变成一个个小方框,负号显示成一条横杠,截图放论文里非常掉价。

原因:matplotlib 默认字体是 DejaVu Sans,不含中文字符集;同时负号渲染规则和常见中文字体不兼容。

解决:在绘图脚本开头加两行plt.rcParams,指定SimHei或系统中文字体。如果服务器上没有这些字体,用fm.fontManager.addfont("/path/to/your.ttf")注册一个本地字体文件再指定。axes.unicode_minus=False这行必须和字体配置同时写,少一行都会出现渲染残缺。

5.3 pyecharts 地图打开是白屏:在线地图资源没跟上

现象:weather_map.html在本地浏览器打开,页面一片空白,控制台报错加载 js 文件失败;放在部署服务器上却正常。

原因:pyecharts 2.x 的地图底图 js 默认从 CDN 加载,本地网络或离线环境拿不到china.js资源。

解决:两种思路。简单方案:把 HTML 放到有外网的环境展示。稳妥方案:先在线打开页面让浏览器缓存资源,再复制到离线环境;或者在渲染时用c.render()后手动检查生成的 HTML 里src指向,把对应 js 文件下载到outputs/js/目录,并修改src为相对路径。论文定稿截图用 PNG,答辩现场若断网直接展示截图兜底,不要赌现场网络。

5.4 API 返回缺字段,sklearn 报 NaN 错误:先观察缺失再决定插值还是丢弃

现象:RandomForestRegressor.fit()直接报Input contains NaN,或者训练后模型分数为 0。

原因:归档型天气接口偶尔会缺失某一天的数据,特征构造里rolling产生的 NaN 被丢进训练集,Pandas 的默认行为是保留它们,Sklearn 不接受 NaN。

解决:构造特征后先打印feat_df.isna().sum(),观察缺失分布。如果是后端少量日期缺失,用df[target].interpolate(method="time")按时间插值,而不是粗暴dropna();如果是滚动窗口前几天的正常缺失,用dropna()丢掉即可。最忌讳的是不打印缺失统计就直接建模,报错后才会发现原始数据质量比模型更值得操心。

5.5 回归模型预测出离谱极端值:用分位数截断和业务规则兜底

现象:某天预测最高温 48°C,或者预测值比历史同期最低温还低 20 度,曲线图上出现明显尖刺。

原因:回归模型在特征组合超出训练集范围时,输出可能不受约束。比如序列里出现一个异常高的滑动均值,树模型外推能力弱,就可能给一个偏激的预测。

解决:对预测结果做业务约束。常见做法是y_pred = np.clip(y_pred, df[target].quantile(0.01), df[target].quantile(0.99)),把预测值限制在历史 1% 分位数到 99% 分位数之间;再结合常识判断,7 月北京最高温预测低于 20°C 时,优先排查特征是否正确,而不是直接采信。这个兜底逻辑写在模型后、画图前,能消掉九成以上的“看起来像代码算错了”的质疑。

6. 用回测验证预测结果,把项目从“能跑”练到“敢写进论文”

很多项目的通病是“能跑,但经不起追问”。面试官或答辩老师问一句“你这个模型准确率是怎么算出来的”,如果只能回答“我用后 30 天测了一下”,说服力是不够的。我最后补一道滚动回测:把历史数据切成多个训练集合验证集窗口,依次向后推进,得到一组稳定的误差分布,而不是单次划分的偶然结果。

def walk_forward_validation(feat_df, train_window=200, step=30, horizon=1): mae_list, rmse_list = [], [] feature_cols = [c for c in feat_df.columns if c != "target_next"] start = train_window while start + horizon <= len(feat_df): train = feat_df.iloc[start - train_window:start] test = feat_df.iloc[start:start + horizon] model = RandomForestRegressor(n_estimators=300, max_depth=6, min_samples_leaf=3, random_state=42) model.fit(train[feature_cols], train["target_next"]) pred = model.predict(test[feature_cols]) mae_list.append(mean_absolute_error(test["target_next"], pred)) rmse_list.append(np.sqrt(mean_squared_error(test["target_next"], pred))) start += step print(f"滚动回测: MAE={np.mean(mae_list):.2f}±{np.std(mae_list):.2f}°C") print(f"滚动回测: RMSE={np.mean(rmse_list):.2f}°C") return mae_list, rmse_list

滚动回测的三个参数决定了验证成本:train_window=200表示每次用约 200 天数据训练,step=30表示每 30 天滚动一次,horizon=1表示只预测下一天。这份验证跑完,论文里可以写“模型在 12 个月滚动回测中平均绝对误差 1.9°C”,比单次划分更有分量。误差评估我固定看两个指标:MAE 反映平均偏差幅度,RMSE 放大极端误差,RMSE 明显大于 MAE,说明模型在个别天严重失准,值得去翻那几天的数据是不是缺字段。最后把回测结果画成误差分布直方图放进论文,配合趋势图,这份毕业设计的完整度已经超过大多数只跑通模型的项目了。

我把这个习惯保留到了现在:改任何参数,先跑回测再看单次结果,不为一个好看的分数自我欺骗。希望你做完这个天气预测项目后,也能带着这份数据判断力去面对更复杂的项目,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询