☰
基于Python的天气数据分析与预测系统设计与实现
2026/10/6 3:58:26 网站建设 项目流程

最近折腾了一个有意思的小项目:基于Python的天气数据分析预测系统。事情起因其实挺简单,我所在的位置通常和城市中心天气预报站点有十几公里的距离,手机天气App上写“多云,25度”的时候,我这边可能是阴天还带阵风。商业预报做不到每个人的具体位置都精准,那么能不能自己用Python搭一套基于历史观测数据的小型预测系统,针对特定地点做未来24到48小时的温度、降水概率分析?顺着这个想法,我把数据获取、清洗、特征构造、模型训练到最后可视化验证的完整链路都走了一遍,踩了不少坑,也沉淀了不少经验。这篇内容就围绕这个系统的设计与实现展开,适合刚学完Python基础、想找一个完整数据分析项目练手的人,也适合想给自己做一个“私人天气预报”的爱好者参考。

1. 项目从哪里开始:需求拆解和整体方案

动手之前我先把问题拆了一遍。天气预测这件事,市面上有两条技术路线:一条是大型机构的数值预报,靠着超级计算机求解大气运动方程,我们个人基本碰不了;另一条是纯数据驱动的统计预测,历史观测数据喂给模型,让模型自己找规律。个人项目要跑得动、要能落地,明显应该走第二条路。

我的目标定得很具体:不追求精确预测每一个时刻的温度,而是预测未来24小时内的最高温度、最低温度,以及降水概率区间。这三个指标足够日常决策使用。整体架构分四层:数据层(定时抓取天气观测数据)、存储层(本地落库成结构化数据)、分析层(清洗、特征工程与模型训练)、应用层(预测结果可视化和导出)。

方案要克制,工具链能省则省。我最终选了requests做数据请求、pandas做数据处理、scikit-learn做模型训练、matplotlib做可视化,最后用APScheduler加了个定时任务每天自动跑一次。没有上Spark、没有上Hadoop,因为数据量根本到不了那个级别,单机杀鸡用牛刀也平白增加维护负担。顺着这个思路,先把数据源搞定。

2. 气象数据从哪来:数据源选型和抓取策略

2.1 公开API与爬虫的取舍

做天气分析的第一道坎就是数据。市面上的天气数据来源无非三类:官方气象部门开放的接口、商业天气API平台、自己爬网页数据。直接爬网页维护成本太高,网页改版反爬策略一变项目就废了,除非只是做一次性爬虫练习,否则不推荐作为长期数据来源。

我当时把几个主流数据源对比了一遍:

数据源数据精度获取难度更新频率适用场景
国家气象数据中心开放平台小时级/日级需要申请,接口不统一延迟较大长期历史数据研究
和风天气API分钟级/小时级注册即用,免费额度有限实时国内地点预报与实况
OpenWeatherMap小时级/日级注册即用,免费额度有限实时全球城市,英文环境
爬虫方式抓取天气网站取决于源站简单但脆弱实时短期实验,不推荐长期用

我最终用的是开放API为主、手动备份为辅的模式。每天定时抓取两个东西:一是当前实况数据(温度、湿度、气压、风速、天气现象),二是未来三天的预报数据。预报数据拿回来有两个用途:短期来看可以作为对照参考,长期来看等目标时间过去之后,这份“预报值”和“实况值”就能组成一条训练样本,让模型去学“当观测条件是这样时,实况最终变成了那样”。

2.2 数据抓取模块的写法

代码结构非常简单,核心就是构造请求、解析JSON、附加时间戳落库。需要注意的是,绝大多数天气API返回的温度在某些区域体系下可能是华氏度,务必先确认单位再入库,不然后面特征构建全是错的。

import requests import pandas as pd from datetime import datetime # 以OpenWeatherMap为例的请求示例 API_KEY = "your_api_key" LAT, LON = 39.9042, 116.4074 # 替换成你的目标经纬度 url = f"https://api.openweathermap.org/data/2.5/onecall" params = { "lat": LAT, "lon": LON, "exclude": "minutely,hourly,alerts", "units": "metric", "appid": API_KEY } resp = requests.get(url, params=params) data = resp.json() current = data["current"] daily_forecast = data["daily"][0] record = { "record_time": datetime.now(), "temp_current": current["temp"], "humidity": current["humidity"], "pressure": current["pressure"], "wind_speed": current["wind_speed"], "weather_desc": current["weather"][0]["description"], "forecast_max_temp": daily_forecast["temp"]["max"], "forecast_min_temp": daily_forecast["temp"]["min"], "forecast_pop": daily_forecast.get("pop", 0) } df = pd.DataFrame([record]) df.to_csv("weather_records.csv", mode="a", header=False, index=False)

这里有一个容易忽略的细节:天气API免费版通常限制调用次数,比如每分钟60次、每天1000次。对于个人项目一天拉一次根本跑不满,但如果你手动测试时反复刷新,很容易触发限流。稳妥的做法是每次请求之间加上sleep,并且把抓到的原始数据完整保留一份,万一后续要换特征、改目标变量,不用重新拉历史数据。

2.3 数据落库:CSV够用,但SQLite更安心

初期我只用了CSV追加写入,简单直接。但跑了几天后发现问题:重复跑脚本导致重复记录、日期字段格式不统一、同时读写容易丢数据。后来我改成SQLite,建表写数据,查询和去重都方便多了。对于这种规模的项目,SQLite比MySQL更合适,零安装、单文件、Python内置sqlite3模块直接操作。

import sqlite3 conn = sqlite3.connect("weather.db") c = conn.cursor() c.execute(""" CREATE TABLE IF NOT EXISTS weather_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, record_time TEXT, temp_current REAL, humidity INTEGER, pressure REAL, wind_speed REAL, weather_desc TEXT, forecast_max_temp REAL, forecast_min_temp REAL, forecast_pop REAL, UNIQUE(record_time) ) """) conn.commit()

建表的时候我加了一个UNIQUE约束在record_time上,插入用INSERT OR IGNORE,天然去重。这个习惯值得养成——凡是带时间戳的数据追加入库,都先设计好唯一键,不然“重复抓取”会成为你排查问题时的噩梦。

3. 数据清洗和特征工程:真正决定预测精度的环节

很多初学者拿到数据就急着调模型,其实整个项目里数据清洗和特征工程花的时间占比最大,也最影响最终效果。天气数据表面干净,但细看全是坑:传感器断线导致的缺测、极端值毛刺、时区不一致、前后两条记录时间间隔不均等。

3.1 缺失值与异常值处理

对于时间序列数据,缺失值不能用全局均值填充,那样会把时间上的渐变趋势抹掉,正确做法是前后插值。比如温度是连续变量,用线性插值就够了;天气现象是分类变量,缺失时用前一条记录填充(forward fill)更合理,因为天气状态在短时间内一般不会突变。

异常值的识别比填充更重要。我遇到过一条记录温度直接从25度跳到40度再跳回25度的情况,原因是设备故障或临时过热。针对这种突变,我计算了相邻两小时温度变化率,凡是变化率超过5度/小时的样本单独标记出来,在训练集里剔除。这个思路其实就是最简单的“阈值型异常检测”,对气象数据来说足够了。

3.2 目标变量定义和特征列表

模型要预测的目标,我定义为:某个回归点之后24小时内的实况最高温度。这里的“某个回归点”是建模范式里最需要注意的地方——也就是你生成一条训练样本的时间点。特征只能用这个时间点之前已知的信息,目标是这个时间点之后未知的结果。如果时间边界没划清楚,数据泄漏就会让模型在验证时“预测很准”、上线后全面拉胯。

我用到的特征分成四类:

特征类别具体特征说明
实时观测当前温度、湿度、气压、风速、天气现象对应当前大气状态
时序滞后项前1h、前3h、前6h、前12h的温度变化趋势捕捉短时演变趋势
周期性特征小时、星期几、一年中的第几天反映昼夜和季节周期
统计特征过去24h平均温度、最高温度、最低温度反映当日整体冷暖背景

周期特征很重要,很多初学者会忽略。温度有非常明显的日周期和年周期规律,直接丢一个“小时”整数给模型,模型会认为12和13是两个无关的数值,但本质上23点和0点都是深夜、非常接近。把小时、天数转成sin/cos组合,才能让模型理解周期性。

3.3 时序数据切分:为什么不能随机打乱

这是整个项目中最容易翻车的步骤。常规机器学习流程里,训练集和测试集是随机切分的,但时序数据一旦随机打乱就完蛋了——模型偷偷看到了未来的数据,验证集上的分数会虚假地高。正确做法是严格按时间顺序切分:比如前80%的时间段作为训练集,后20%作为测试集。

更严谨一点可以用时间序列交叉验证,也就是滑动窗口式地训练多次,每次用更晚的数据做验证。当时我用的是手动实现:训练集取连续365天数据,验证集取紧接其后的30天数据,绝不交叉。边界处我留了一个空隙,避免滞后特征跨越时间边界造成泄漏。

4. 模型选型过程:从线性基线到非线性模型的梯度

4.1 先从最简单的基线开始

建模的第一步不是直接上LightGBM,而是先构造一个朴素基线。对天气预测来说,最经典的基线就是“持续性预测”——明天的最高气温等于今天的最高气温。听起来荒谬,但实际预测效果意外地好,因为在没有强冷空气或锋面过境的稳定天气条件下,温度日际变化本来就很小。如果你的模型连持续性基线都打不过,那说明特征或模型设计有问题,没必要继续优化。

我同时跑了两个基线:一个是用“今天最高温度”直接作为“明天最高温度”预测值的持续性基线,一个是简单的线性回归。线性回归在特征工程做扎实之后效果并不差,而且可解释性极强,可以清楚看到每个特征对温度的贡献方向。比如湿度系数为负,代表湿度越大、最高温越倾向于偏低,这在梅雨季节是符合直觉的。

4.2 为什么最终选了梯度提升树

线性模型加了一些交互特征之后,我开始试常见的非线性模型。LightGBM是我最后固定下来的选择,对比结果如下:

模型测试集MAE(温度,℃)训练时间说明
持续性基线2.310秒每天预测值等于当天值
线性回归1.87秒级特征标准化后效果稳定
随机森林1.76秒级比线性回归略有提升
LightGBM1.62秒级相对最优,调参空间大
LSTM2.20分钟级数据量不够,反而不如树模型

看到这个结果我挺意外的:LSTM在这个问题上输给了LightGBM。原因不复杂,LSTM需要大量序列数据来学习长期依赖,而我手上只有几百天的日频样本,序列太短、噪声太大,强行上深度学习只会过拟合。这也给后来的项目定了规矩:模型复杂度要和数据量匹配,不是为了酷炫而上复杂模型。

4.3 训练代码和参数心得

训练部分代码很常规,但有两个细节值得拿出来说。第一是特征标准化,线性模型必须做,树模型做不做影响不大,但统一做一遍省心;第二是目标值如果有明显的季节性趋势,可以按月份拆分训练,让每个月的模型只学当月的温度规律,这个技巧效果提升显著。

from lightgbm import LGBMRegressor from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline feature_cols = ["temp_current", "humidity", "pressure", "wind_speed", "temp_trend_1h", "temp_trend_3h", "temp_trend_6h", "hour_sin", "hour_cos", "day_sin", "day_cos", "avg_temp_24h", "max_temp_24h", "min_temp_24h"] X = df[feature_cols] y = df["target_max_temp"] model = Pipeline([ ("scaler", StandardScaler()), ("lgbm", LGBMRegressor( n_estimators=300, learning_rate=0.05, max_depth=5, num_leaves=31, subsample=0.8, colsample_bytree=0.8, random_state=42 )) ]) model.fit(X_train, y_train) y_pred = model.predict(X_test) print(f"Test MAE: {mean_absolute_error(y_test, y_pred):.2f} °C")

LightGBM的调参核心是max_depth和num_leaves要一起控制,否则很容易过拟合。我的经验是先用默认参数训练一遍var(X_test)看看,如果训练集MAE远小于测试集,说明过拟合,再降深度和叶节点数。天气预测场景不需要追求极高精度,MAE在1.5-1.8°C之间已经足够日常参考了。

5. 预测结果评估:如何判断模型真的可用

模型训练完,不能只看一个MAE就收工,这个数字会骗人。我当时踩过最大的坑是:整体MAE看着还行,但一点开分月统计,夏季和冬季误差远大于春秋季,雨天更是重灾区。

5.1 误差分层分析

我把测试集按月份、按天气现象、按温度区间分了组,分别计算MAE,发现几个规律:

  • 温度越高的日子,误差越大。因为高温天气多伴随午后热对流,温度波动剧烈,预测难度天然更高。
  • 雨天误差比晴天大约高20%-30%,原因类似,云量和降水带来的辐射平衡变化很难从历史数据中精确还原。
  • 夜间最低温度的预测误差比白天最高温度要低,因为夜间变量少,云量影响相对可控。

通过这些分组分析,我明确了一个认知:单点MAE是“平均之下的幸存者偏差”,分组误差分布才是模型改进的指挥棒。如果你的模型在某个细分场景下误差明显偏大,优先补那个场景的特征,而不是盲目调参。

5.2 可视化诊断三张图

可视化不是给汇报用的,是用来发现问题的。我每次迭代模型后必画三张图:预测值与真实值的散点图、残差分布图、误差随时间的变化图。

散点图能看出模型是否系统性偏低或偏高。如果散点在低温端偏上、高温端偏下,说明模型有“回归均值”倾向——预测结果偏向历史平均,极端温度被拉平。残差图如果呈现漏斗形,说明方差不稳定,小时段和稳定时段混在一起了。

画图代码很简单:

import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) plt.scatter(y_test, y_pred, alpha=0.5) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], "r--") plt.xlabel("真实最高温度") plt.ylabel("预测最高温度") plt.title("预测值与真实值对比") plt.show() residual = y_test - y_pred plt.figure(figsize=(10, 4)) plt.plot(residual) plt.axhline(0, color="r", linestyle="--") plt.xlabel("样本序号") plt.ylabel("误差") plt.show()

如果散点图显示模型系统性偏低,可以在最终预测上加上一个校正量,但这个校正量必须通过验证集计算,不能是肉眼估计的。

5.3 降水概率怎么评估

温度是回归问题,降水概率则是分类或概率预测问题。我这里用的方法很朴素:按历史天气现象文本是否包含“雨”字作为真实标签,模型输出一个0到1的概率值,再画ROC曲线算AUC。AUC能到0.85以上对于个人项目就算不错了。因为降水概率本质上是一个极不平衡问题,晴天样本远多于雨天样本,所以看准确率没有意义,必须看AUC或看不同概率阈值下的命中率。

6. 部署运行和长期维护中的实际问题

6.1 定时任务和数据连续性

模型训练好之后,真正让它每天自动跑起来,靠的是APScheduler或者操作系统的定时任务。我最终选择了服务器上的cron,每天清晨6点拉一次数据、重训练一次模型、生成当天预报。为什么每天重训练而不是训练一次一直用?因为天气的季节特性明显,模型需要持续看到最新数据才能跟上季节转换,比如入冬后第一次寒潮来临,如果模型还是用上个月的数据训练,预测就会明显偏高。

# 每天6点执行预测 0 6 * * * cd /path/to/project && python predict.py >> logs/predict.log 2>&1

重训练的频率不能太高,对个人项目来说一天一次正好,数据量也会积少成多。

6.2 时间戳时区问题

这是我在项目中期才发现的一个大坑。天气API返回的时间多数是UTC时间,我本地存储时如果直接用北京时间,会导致某些特征错位。当时我在拉历史数据时,把UTC时间直接存成了字符串,等到构造滞后特征时才发现“前6小时温度”这个特征,取到的其实是同一时刻的乱序数据。后来我统一在入数据库之前把时间全部转成北京时间并加后缀,特征构造时全部以解析后的datetime对象为准,彻底解决了这个问题。

这个坑影响很大,因为它不是报错的,只是让你预测悄悄变差。如果你也做类似项目,建议第一步就确认所有时间字段的时区,并统一转换成目标时区的datetime对象。

6.3 极端天气预测的系统性偏差

还有一个很难根除的问题:模型在极端高温和极端低温出现时总是预测得不够极端。这是我前面提到的“回归均值”现象。统计学上这是正常的,因为训练数据本身就围绕平均分布,极端值样本量少、模型没有足够证据去预测极端。如果要改善,可以做分位数回归,让模型输出预测区间的上下边界,而不是单一数值。我当时没继续深挖,但对“预测区间”这个概念印象很深——给决策用的预测,单点值永远不如区间实用。

7. 最后想说的几条实在话

这个项目从开始萌生想法到跑通全流程,前后经历了几轮迭代。我最深的体会是,做数据分析项目最大的乐趣不是模型准确率从1.9降到1.62的那一刻,而是把一条完整链路搭建起来、让它每天自动运行、并能够稳定输出可读结果的那种掌控感。纯Python、requests、pandas、LightGBM,这些工具单个拎出来都不难,难的是如何把它们串成一个能长期运行的系统。

如果你也想做这类项目,我的建议是把“预测准不准”这个执念先放下,集中精力把数据记录、清洗、特征构造这三个环节做得越扎实越好。基础打牢之后,哪怕你用的是最简单的线性回归,也能得到一个比大多数天气预报App在特定点位上更符合你实际体感的参考结论。天气数据只是起点,同样的方法换成股票价格、电力负荷、空气质量,逻辑完全一样——把历史数据变成结构化特征,再让模型挖掘规律,这件事本身就值得做一遍。

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

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

立即咨询