简介:基于Python的气象数据分析与可视化项目源码包,面向Python数据分析初学者及需要完成期末大作业的高校学生,提供从数据获取、清洗、建模到可视化展示的完整流程参考。包内共22个文件,其中4个py脚本分别承担数据抓取、预处理、模型训练与主程序调用等环节,4个csv文件提供训练集、测试集及城市实时天气数据,1个pkl文件保存训练好的预测模型,12张jpg图片为各步骤运行结果截图,1个html文件呈现可交互的天气可视化页面;整体仅1.42MB,结构清晰、压缩包小巧,便于直接运行与二次开发。该项目为个人大作业成果,项目评审分达95分以上,源码经严格调试确保可运行,目前已有509人学习下载。通过这份资源可快速掌握天气数据预处理、机器学习预测及可视化图表的生成思路,适合作为课程设计、毕业设计或自学实践的参考模板。
1. 气象数据分析实战,大多数人会直接卡在第一步
用Python做气象数据分析,听起来是一条完整链路:爬数据、清洗、建模、可视化。但真正动手拆这份期末大作业源码时你会发现,这套系统里最有价值的不是模型本身,而是它把一条完整的数据流水线拆成了可复用的五个脚本,从天气网抓取、CSV清洗、模型训练、预测到生成交互式HTML页面全部串通。尤其适合两类人:一类是正在做课设但卡在“数据从哪来”的同学,可以把这套代码当作现成的爬取与预处理脚手架;另一类是已经会调库但不懂工程化组织代码的入门从业者,可以看看别人是怎么管理中间文件的。这篇拆解会沿着文件清单逐层深入,把每条命令要改的参数、每个坑在哪、怎么验证结果说清楚,让你在跑通代码之外,真正能用这套结构套到别的分析任务里。
2. 拆包看结构:五个脚本一条链路,谁在等谁的输出
拿到压缩包先别急着运行,源码包的文件名其实已经暴露了完整的执行顺序。压缩包里有两类文件:一类是脚本,另一类是中间产物。搞清楚哪些是生成的、哪些是你必须自己准备的,跑起来才不会缺文件报错。
2.1 文件清单与依赖关系
这份资源的核心可执行文件是五个:GetData.py、ProcessData.py、GetModel.py、main.py,以及一个HTML模板文件“天气网.html”。数据文件里,date_train.csv、date_test.csv、date_valid.csv是划分好的训练/测试/验证集,china_today.csv是当天全国天气快照,Model.pkl是训练产出的模型文件。wps开头的那些图片是作者当时生成的图表截图,相当于运行结果的参考样例,不是必须文件。
依赖关系如下:GetData.py负责从天气网抓取原始数据并生成date_train.csv等文件;ProcessData.py读入原始CSV,做日期解析、数值清洗,输出清洗后的中间表;GetModel.py读取处理后的训练数据,训练模型并保存为Model.pkl;main.py加载pkl和china_today.csv,做预测并渲染天气网.html。图片里那些wps图表,本质上是main.py输出的matplotlib图表的截图归档。
安装依赖之前,建议先核对Python版本。这套代码按Python 3.6以上的语法写的,用了f-string,用Python 3.8或3.9最稳。依赖主要是requests、pandas、numpy、matplotlib、scikit-learn。如果环境里已装了Anaconda,直接用conda创建一个干净环境最省事:
conda create -n weather python=3.9 conda activate weather pip install requests pandas numpy matplotlib scikit-learn这里的逻辑是先用conda隔离环境,再用pip装纯Python库,避免把系统Python搞乱。很多人在自己机器上跑别人的课设源码,报错往往是版本冲突,比如pandas老代码用了append方法,新版pandas会提示被移除;先建独立环境,这类问题少一半。
2.2 先跑哪一步:脚本执行顺序不能乱
因为脚本之间有强依赖,执行顺序错了会报“FileNotFoundError”或“Model.pkl not found”。正确顺序是先GetData再ProcessData再GetModel最后main。我的习惯是拿到开源代码先不看算法,直接把每个脚本的__main__入口扫一遍,你就能在30秒内画出调用链。
比如GetData.py里通常会有类似这样的入口:
if __name__ == "__main__": load_history_data()ProcessData.py里可能既有独立的工具函数,也有对刚才生成CSV的再加工入口。这里最关键的一条经验:如果没把握顺序,用PyCharm或VSCode的调试模式,在ProcessData.py开头打上断点,看它import了哪个模块、读取了哪个文件路径,就明白前置依赖是什么。路径通常是相对当前工作目录的,所以命令行运行时要在脚本所在目录执行,否则文件找不到。
3. 数据获取与预处理:从天气网抓取到date_train.csv的清洗细节
这一章是整个资源里最容易被当黑匣子跳过、但实际上最影响结果的部分。气象数据的特点是:字段类型混乱、日期格式不统一、缺失值占比不低。直接把原始数据丢给模型,训练出来的准确率会很难看。
3.1 抓取脚本的工作方式与请求参数
GetData.py按常见的城市和日期参数,从天气网按天抓取天气记录。典型代码会定义城市ID列表和历史日期的起始范围,通过requests.get拼接URL,再用正则或pandas.read_html解析表格。实际操作中,一个适合教学课设的做法是:
import requests import pandas as pd def fetch_weather(city_code, start_date, end_date): base_url = "https://example-weather-api.com/data" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Referer": "https://example-weather-api.com/" } date_range = pd.date_range(start=start_date, end=end_date, freq="D") rows = [] for single_date in date_range: params = { "city_code": city_code, "date": single_date.strftime("%Y%m%d"), } resp = requests.get(base_url, params=params, headers=headers, timeout=10) data = resp.json() rows.append({ "date": single_date, "high_temp": data.get("high", ""), "low_temp": data.get("low", ""), "weather": data.get("weather", ""), "wind": data.get("wind", "") }) return pd.DataFrame(rows)注意这里的核心技巧:headers里的User-Agent必须设置,不设置会被部分站点直接拒绝;timeout参数要加,防止某个日期请求挂起导致全脚本卡死。参数说明上,city_code对应城市编号,start_date和end_date控制抓取区间,这两个参数决定了训练集的时间跨度。很多人新学的爬虫会忽略拉取频率控制,同一个站点连续请求时最好在循环里sleep 1到2秒,这也是为什么完整版脚本通常会在这里加一个time.sleep。
3.2 数值列清洗和日期解析的硬规则
ProcessData.py承担的是核心的数据清洗。气象数据最常见的三个活儿是:日期字符串标准化、温度列从字符串里提取数字、风向风速拆分。以温度为例,原始数据里常见的是“23℃/12℃”这种格式,必须转成两列数值。一个稳妥的写法是:
import pandas as pd import re def parse_temperature(temp_str): if pd.isna(temp_str): return None, None nums = re.findall(r"-?\d+", str(temp_str)) if len(nums) == 2: return int(nums[1]), int(nums[0]) if len(nums) == 1: return int(nums[0]), int(nums[0]) return None, None def clean_weather_df(raw_df): df = raw_df.copy() df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d", errors="coerce") df[["high_temp", "low_temp"]] = df["temp_str"].apply( lambda s: pd.Series(parse_temperature(s)) ) df = df.dropna(subset=["date", "high_temp", "low_temp"]) df = df.sort_values("date").reset_index(drop=True) return df这段代码的逻辑是用正则一次性提取温度字符串中的正负整数,然后按high/low的顺序填入两列。用errors="coerce"非常关键,这样解析失败的日期会变成NaT,后边的dropna会把这些脏行物理删除,不会让脏数据混进训练集。参数说明:format里写了“%Y-%m-%d”,如果原始数据里是斜杠格式,就要改成“%Y/%m/%d”,这是最常见的翻车点。
3.3 数据划分的时间顺序陷阱
date_train.csv、date_test.csv、date_valid.csv这三个文件的划分方式,在气象预测任务里一般是按时间顺序切的,而不是像经典机器学习那样随机打乱。原因很简单:天气预测是按时间序列外推,如果用随机抽样划分,训练集里出现未来日期、测试集里出现过去日期,模型相当于作弊,评估分数会虚高。检查代码时会发现ProcessData.py里通常有类似df.iloc[:7000]这种切片方式,前70%做训练,中间15%做验证,最后15%做测试。
这里有一个必须提醒的坑:时间序列数据不能直接用scikit-learn的train_test_split,除非你设置了shuffle=False。见过不少同学在这里翻车,切成随机切片之后,模型跑出来的准确率特别高,换个时间区间马上失效,这就是数据泄漏。验证的办法很简单,打印出训练集和测试集的日期区间,如果月份有交集,逻辑就有问题。
4. 机器学习建模与模型落盘:天气预测用哪种模型、参数怎么设
数据清洗完之后,GetModel.py负责训练模型。这个脚本选择什么样的算法作为baseline,决定了整个项目能拿到多少分。很多期末大作业项目会直接上LSTM或Prophet,但这份资源用的是scikit-learn体系内的模型,主要原因有两个:一是数据量不大,东北部某站的逐日数据也就几千行,深度学习难以发挥;二是课程的要求一般落在数据分析和可视化上,不是追求SOTA精度,可解释性反而更重要。
4.1 特征工程的构造方式
训练前最重要的工作是把日期转换为周期性特征。直接拿日期字符串做特征没法训练,常见做法是把日期拆成年、月、日、星期几、是否节假日等。更进一步,可以把“月”和“日”转成周期性编码,用sin/cos函数保留相近日期在特征空间里的邻近关系:
import numpy as np import pandas as pd def add_temporal_features(df): df_feat = df.copy() df_feat["year"] = df_feat["date"].dt.year df_feat["month"] = df_feat["date"].dt.month df_feat["day"] = df_feat["date"].dt.day df_feat["dayofweek"] = df_feat["date"].dt.dayofweek df_feat["month_sin"] = np.sin(2 * np.pi * df_feat["month"] / 12) df_feat["month_cos"] = np.cos(2 * np.pi * df_feat["month"] / 12) df_feat["day_sin"] = np.sin(2 * np.pi * df_feat["day"] / 31) df_feat["day_cos"] = np.cos(2 * np.pi * df_feat["day"] / 31) return df_feat这里的参数说明很重要:month_sin和month_cos合在一起保留的“月份相近”信息,让1月和12月可以互相靠近。如果你只放原始月份值,模型会认为1月距离12月很远,但实际上它们在季节上是相邻的,这个正弦编码是气象时间序列特征工程里很经典的技巧,比单调递增的数值列合理得多。
4.2 回归模型的选择与调参建议
温度预测属于回归任务,我倾向于让模型对比随机森林和梯度提升回归器。随机森林的优势是不需要精细调参就能得到一个能看的baseline,默认n_estimators=100时效果就过得去。梯度提升回归器则能进一步压小误差,但需要防过拟合。GetModel.py里的训练模式大致是:
from sklearn.ensemble import RandomForestRegressor import joblib features = ["year", "month", "day", "dayofweek", "month_sin", "month_cos", "day_sin", "day_cos"] X_train = train_df[features] y_train = train_df["high_temp"] model = RandomForestRegressor(n_estimators=200, max_depth=10, min_samples_leaf=5, random_state=42) model.fit(X_train, y_train) joblib.dump(model, "Model.pkl")关键参数是max_depth和min_samples_leaf。课设项目的数据量几千行,max_depth不设限制的话,树很容易长到很深,总是完美拟合每个训练样本,验证集误差反而上浮。min_samples_leaf=5是防止叶子节点样本太少导致方差过大。random_state=42固定随机种子,两个随机性来源必须固定:一是模型内部的bagging抽样,二是特征分裂时的随机候选特征选择。
4.3 模型验证与得分报告
训练完之后,代码会在验证集上计算平均绝对误差MAE和R²得分。这里最值得抄的一件事是作者把真实值和预测值的折线图画在同一张图上,然后分别对训练集和验证集做对比,能直观看出模型是否过拟合。判断标准很朴素:训练集误差低、验证集误差高,那基本就是过拟合。把验证集MAE和训练集MAE同时打出来,相差超过2℃你就要回头调整上边的max_depth。
Model.pkl的落盘用joblib.dump,加载时用joblib.load,这比pickle更稳,因为它对numpy数组和大型树模型的序列化优化更好。注意如果换机器运行,Model.pkl只包含模型参数,不包含训练数据,运行时只需要特征列存在,匹配main.py里的处理逻辑即可。
5. 可视化落地与排查:天气网.html的动态渲染和常见问题
main.py做的事是串联最终预测结果并生成页面。它读取便于展示的china_today.csv和Model.pkl,对每天的温度做预测,最后把图表和数据写进“天气网.html”模板里。这个文件用交互式网页这种方式来承载预测结果,比在Jupyter Notebook里展示更容易交给老师或客户验收,因为对方只需要打开浏览器。
5.1 HTML渲染与matplotlib图表保存
常见的做法是用matplotlib生成图表并保存为base64编码嵌进HTML,而不是让页面动态请求图片文件。这样交付给别人时只要单个HTML文件就能全部展示,不会丢图。先展示典型的matplotlib中文乱码规避写法:
import matplotlib.pyplot as plt import base64 from io import BytesIO plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = False fig, ax = plt.subplots(figsize=(12, 5)) ax.plot(real_dates, real_temps, label="实际温度", linewidth=1.5) ax.plot(predict_dates, predict_temps, label="预测温度", linewidth=1.5, linestyle="--") ax.set_title("未来一周最高气温预测") ax.legend() buf = BytesIO() plt.savefig(buf, format="png", dpi=150, bbox_inches="tight") img_base64 = base64.b64encode(buf.getvalue()).decode("utf-8") plt.close(fig)核心逻辑是先用BytesIO把图存到内存流里,再转成base64字符串,通过HTML的img src="data:image/png;base64,..."注入模板。如果不设置plt.rcParams["font.sans-serif"],图里的中文会变成方框。还有就是必须在savefig之后立即plt.close,否则连续生成多张图时内存会一直涨,不仅每次图表重叠,严重时还会拖垮后续渲染。
5.2 常见问题排查:三个高频报错
这里跳到一个独立章节,集中写这套资源最常遇到的报错和排查方案。
现象一:运行GetData.py报requests.exceptions.ConnectionError。原因大多不是代码问题,而是目标站点在第一次请求后校验了SSL证书,或者本机网络环境对出站请求做了限制。解决方法是先独立测试目标域名是否可用,再在requests.get里加verify=False,并给请求头补全Accept与Accept-Language字段。
现象二:ProcessData.py运行完没有报错,但打开date_train.csv发现所有温度列都是空。原因是我之前说的正则提取失败,因为原始数据里温度格式可能是“23.5℃”这种带小数点的格式,而正则只匹配整数。解决方法是统一先用replace去掉“℃”保留纯数字部分,再判断是否需要转浮点。
现象三:main.py渲染HTML时,模板里的大括号和f-string冲突,导致页面只显示一串Python代码。原因是f-string里使用了大括号来表示CSS样式块。解决方法是把CSS部分保留为普通字符串拼接,f-string只处理动态的图片和表格数据,或者用字符串模板的replace方法替换占位符。
5.3 更换城市与重新抓取数据的完整流程
这份资源最容易被忽略的扩展点是城市维度的替换。操作流程分成三步:第一步在GetData.py里更换city_code为新的城市编码;第二步把start_date往前拉长到至少五年,保证数据量足够;第三步重新执行GetData、ProcessData、GetModel三个脚本,最后再看main.py的输出。注意改城市后旧Model.pkl必须删除重新生成,否则用了之前城市训练的模型来预测新城市的数据,结果完全不可信,而且不会有任何报错提示,是隐性的逻辑错误。
6. 把气象预测拆成可复用模板:从单次跑通到换数据源也能用的工程习惯
整套代码跑通之后,我建议你别满足于“能出图”这个结果。这份资源真正的价值在于,它的文件划分方式本身就构成了一套小型数据处理管线模板,换个数据源、换一个时间序列预测任务,结构能直接复用。以下三个习惯能让你把它改造成自己的工具包。
第一个习惯是固定中间产物命名。date_train.csv、date_valid.csv这类命名一旦在脚本里被多处引用,就不要随意改成带版本号的类似“temp_20241001.csv”的命名方式。如果非要改,那就同时修改所有import这些文件的脚本。更稳的做法是单独维护一个config.py的配置项,把输入输出文件名集中管理,避免文件路径散落在各个脚本里。这样做的好处是,当你要把训练数据从天气数据换成股票日线数据时,不需要打开ProcessData.py一行行地毯式搜索所有出现日期解析的地方。
第二个习惯是给每个脚本的必要输出打印摘要行。每次跑完ProcessData.py后,强制print出数据框的形状、列名、日期范围,做到任何一次异常都能立刻发现。从这套源码里我能看出当时作者在调试的时候没少做这件事,因为中间产物的命名和对齐程度,明显是经过好几轮人工检查的。你后续接手之后,应该在这个基础上保留print摘要的写法,而不是把它删掉,因为删掉后运行一套流水线后只能看到黑匣子,出了问题完全不知道哪个环节错了。
第三个习惯是版本留痕。GetModel.py生成的Model.pkl在多次调参后可能被新模型覆盖。翻车过一轮之后,我现在每次重新训练前都强制把上一版拷贝成Model_backup.pkl。这样如果新模型效果反而变差,还有后悔药可以吃。对实验性的改动,最好在文件名上附带时间戳,比如Model_1210.pkl,然后在main.py的加载处把文件名常量抽出来,方便快速回到旧版本。这是很多教学代码里不教的工程习惯,但实际做项目和课设答辩时非常加分,至少被问到“如果模型效果不好怎么回退”时,你不会懵。
最后,如果你也是自学的从业者而不是在校学生,建议把这份资源里所有直接抓取逻辑改为读取本地CSV的接口,这样既能保留完整流程,又不会因为站点改版而全部失效。用这套结构做完一个可复现的时间序列预测Demo之后,再把爬虫部分换成别的公开数据源,就能在实际工作中直接复用了。希望这套拆解能帮到你,至少遇到气象数据项目时,少走几段弯路。
本文还有配套的精品资源,点击获取