Python天气数据爬虫与机器学习温度预测实战解析

Python天气数据爬虫与机器学习温度预测实战解析 简介这是一份面向Python初学者的期末大作业/课程设计完整方案围绕天气数据爬取与可视化分析实现从数据采集、清洗建模到结果展示的闭环流程。资源共24个文件压缩包仅1.42MB其中GetData.py、ProcessData.py、GetModel.py和main.py四个Python脚本分别承担爬虫采集、数据预处理、模型训练与主程序运行配套csv格式训练/测试数据、Model.pkl模型文件以及天气网.html展示页面另有12张jpg截图可直观对照每一步效果readme.md说明文档便于快速上手。项目代码包含详细注释部署简单新手也能按教程完成环境配置并运行适合课程设计、期末答辩或作为爬虫与数据分析入门实战参考。已有926人学习下载功能完善、界面直观具有较高参考与应用价值。1. 从天气数据大作业说起爬虫、清洗、建模一条链很多 python 大作业和课程设计都会选“网络爬虫爬取天气数据及可视化分析”这个方向因为链路完整、演示效果好但真正拿到满分的人并不多。这份项目能给你的不是一段能跑的代码而是把采集、清洗、建模、可视化串成一条工程流水线的套路GetData.py 负责抓数据ProcessData.py 把乱七八槽的网页字段整理成规整表格GetModel.py 训练回归模型并导出 pkl最后 main.py 一键跑通全流程并输出图表。适合期末大作业、课程设计、毕业设计前练手也适合工作两三年的 Python 工程师拿来抄作业。一个反直觉的结论是这个项目里真正难的不是爬虫而是日期对齐和特征构造——数据只要错一天模型分数再高也是假的。2. 数据采集requests BeautifulSoup 抓天气网与参数设置2.1 页面解析与字段抽取项目里保留了天气网.html 这个静态文件说明当时是先用浏览器保存页面再用本地 HTML 做解析调试的。这个思路对大作业来说非常实用服务器随时可能改版或封 IP先把页面存成本地文件解析逻辑稳定后再换线上 URL能省不少时间。抓取核心用 requests 获取页面源码再用 BeautifulSoup 定位数据所在的标签。天气类网站常见结构是ul classt下挂着每一天的li每个li里有日期、最高温、最低温、天气现象、风力等字段。参考 GetData.py 里的做法代码一般长这样import requests from bs4 import BeautifulSoup import pandas as pd HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } def parse_weather(html_text): soup BeautifulSoup(html_text, html.parser) items [] # 存储每条记录 for li in soup.select(ul.t li): # 日期如 2024-06-01 date_tag li.select_one(.date) # 天气现象如 晴 / 多云 / 小雨 wea_tag li.select_one(.wea) # 最高温 / 最低温如 32℃ / 21℃ temp_tag li.select_one(.tem span) high, low extract_temps(temp_tag) # 自定义函数拆分两个温度 items.append({ date: date_tag.text.strip(), weather: wea_tag.text.strip(), high: high, low: low }) return pd.DataFrame(items)extract_temps这一步很关键因为页面里温度可能写在一个标签里也可能是两个标签。我一般这样处理def extract_temps(temp_tag): if temp_tag is None: return None, None # 常见结构span32℃/span/span21℃/span spans temp_tag.find_all(span) if len(spans) 2: return clean_temp(spans[0].text), clean_temp(spans[1].text) # 也可能是直接文本32℃ / 21℃ text temp_tag.get_text( , stripTrue) parts text.replace(℃, ).split() nums [p for p in parts if p.lstrip(-).isdigit()] return (int(nums[0]), int(nums[1])) if len(nums) 2 else (None, None) def clean_temp(s): return int(s.replace(℃, ).strip())参数说明soup.select用的是 CSS 选择器比find_all更贴近前端 DOM 结构调试时也容易对照第一个参数ul.t li的含义是“class 为 t 的 ul 下的所有 li”。temp_tag里如果直接调get_text( )多个 span 之间会用空格连接方便后续 split 拆分这里先尝试提取 span再 fallback 到文本解析是比较稳的写法。2.2 抓取策略与反爬兜底在线抓取时不要直接用 requests.get 裸奔至少要带上Headers里的User-Agent和Referer。很多课程作业被 ban 是因为请求头里是默认 Python-requests 标识一眼被识别。项目里应该也有类似设置建议再加一层session requests.Session() session.headers.update(HEADERS) resp session.get(url, timeout10) resp.encoding resp.apparent_encoding # 避免中文乱码resp.apparent_encoding比resp.encoding更可靠因为天气网页头可能没写 charset或者写的与实际不符。抓取频率上我一般会在两次请求之间加time.sleep(1)大作业数据量不大控制在几十条记录就没有封 IP 风险。如果目标网站有验证码或滑块果断放弃直接换历史数据源或人工保存 HTML。2.3 落盘与增量更新项目输出两个 CSVdate_train.csv和date_test.csv。这说明采集不是一次性而是按时间范围拆开。常见做法是把所有历史数据放到china_today.csv这个全量文件中再用ProcessData.py按日期切分。增量更新时只需要读取已有 CSV 的最后日期只抓新增部分def update_csv(old_file, new_data, date_coldate): try: old pd.read_csv(old_file, parse_dates[date_col]) latest old[date_col].max() new_data new_data[new_data[date_col] latest] merged pd.concat([old, new_data], ignore_indexTrue) except FileNotFoundError: merged new_data merged.drop_duplicates(subset[date_col], keeplast, inplaceTrue) merged.to_csv(old_file, indexFalse, encodingutf-8-sig)注意encodingutf-8-sig是为了 Excel 打开不出现乱码而 pandas 读取时用默认 utf-8 即可。drop_duplicates的keeplast用于覆盖同一天可能抓到的重复脏数据这个比先查出再删除更高效。3. 数据清洗与特征工程ProcessData.py 的关键处理3.1 缺失值与日期对齐采集到的原始数据往往有三个问题日期缺失、温度字段为 None、同一天出现多条记录。ProcessData.py 里最先处理的就应该是日期对齐因为后续训练模型必须依赖连续的时间序列。项目里有date_valid.csv我推测这是一个包含完整日期范围的参考表用于补齐缺失日期。常见做法是先生成完整日期序列import pandas as pd import numpy as np def align_dates(df, start2023-01-01, end2024-06-30): full_dates pd.date_range(startstart, endend, freqD) full_df pd.DataFrame({date: full_dates}) df[date] pd.to_datetime(df[date], errorscoerce) # 丢弃无法解析的日期避免 error 行混入 df df.dropna(subset[date]).drop_duplicates(date) merged full_df.merge(df, ondate, howleft) return merged这里errorscoerce会把异常日期转成 NaT 并丢给后续 dropna 处理比直接pd.to_datetime抛异常更可控。merge用howleft保证日期完整缺失的气象字段则留在后面填充——这些空值可以视为“当天没有抓到数据”而不是“不存在”所以不能直接删除行。3.2 数值化与平滑处理天气现象是分类变量比如“晴”“多云”“小雨”需要编码成数值才能进模型。最简单的做法是 One-Hot 编码但对于日期回归预测来说类别太少会导致维度爆炸。我建议按恶劣程度映射成等级这也是大作业里容易拿分的点weather_map { 晴: 0, 多云: 1, 阴: 2, 小雨: 3, 中雨: 4, 大雨: 5, 雷阵雨: 6, 小雪: 3, 中雪: 4, 大雪: 5 } df[weather_code] df[weather].map(weather_map).fillna(-1)温度为数值型但直接拿最高温和最低温当原始特征模型容易过拟合。常规操作是构造温差和滑动平均df[temp_diff] df[high] - df[low] df[high_ma3] df[high].rolling(window3, min_periods1).mean() df[low_ma3] df[low].rolling(window3, min_periods1).mean()rolling(window3)会让前两条记录产生 NaN加min_periods1可以避免丢掉开头数据。temp_diff的意义在于描述昼夜温差这对预测次日最高温很有用。原始数据里温度如果带“℃”字符需要先清洗df[high] pd.to_numeric(df[high].astype(str).str.replace(℃, ), errorscoerce)3.3 训练/测试集划分项目里直接给出了三个文件date_train.csv、date_test.csv、date_valid.csv。这不是随便切的而是按时间顺序切因为天气数据是时间序列随机切分会让模型“偷看”未来数据。合理划分如下df pd.read_csv(result.csv, parse_dates[date]).sort_values(date) train_end int(len(df) * 0.7) valid_end int(len(df) * 0.85) df_train df.iloc[:train_end] df_valid df.iloc[train_end:valid_end] df_test df.iloc[valid_end:] df_train.to_csv(date_train.csv, indexFalse) df_valid.to_csv(date_valid.csv, indexFalse) df_test.to_csv(date_test.csv, indexFalse)为什么需要三段因为模型要先用训练集拟合再用验证集调参最后用测试集评估泛化能力。很多大作业只切训练/测试两段答辩时会被问“那验证集呢”所以你看到date_valid.csv存在说明原始项目做得很规范。划分比例上 7:1.5:1.5 对几百条数据来说比较常见如果数据达到几千条可以放宽到 8:1:1。4. 模型训练与预测GetModel.py 的回归建模4.1 特征选择与模型选型预测目标一般是“明天最高温”或“明天最低温”。特征不能只用前一天的温度那样模型就是无脑复制。项目里在 ProcessData.py 构造的特征可以用来做滞后特征for lag in [1, 2, 3]: df[fhigh_lag{lag}] df[high].shift(lag) df[flow_lag{lag}] df[low].shift(lag) df[fweather_code_lag{lag}] df[weather_code].shift(lag)shift(lag)的含义是“往前数第 lag 天的值”这能帮模型捕获周期性。模型选型上大作业不推荐 LSTM因为数据量撑不起深度学习随机森林或梯度提升更稳一个重要的原因是它们对缺失值有容忍度而且能输出特征重要性答辩时可以用图表展示。参考 GetModel.py 的命名里面大概率用了RandomForestRegressor我按这个思路写from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, r2_score features [high_lag1, low_lag1, high_lag2, low_lag2, weather_code_lag1, temp_diff, high_ma3] X_train df_train[features].values y_train df_train[high].values model RandomForestRegressor( n_estimators200, max_depth8, min_samples_leaf2, random_state42 ) model.fit(X_train, y_train)4.2 训练与交叉验证随机森林的超参数里n_estimators从 100 加到 200 对精度提升有限但会增加训练时间max_depth限制树深度可以防止模型死记硬背min_samples_leaf保证叶子节点最少有 2 个样本避免过拟合。但这几个值需要验证不能拍脑袋。用sklearn的时间序列交叉验证也就是按时间顺序切组from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) scores [] for train_idx, val_idx in tscv.split(df_train): X_tr, X_val X_train[train_idx], X_train[val_idx] y_tr, y_val y_train[train_idx], y_train[val_idx] model.fit(X_tr, y_tr) y_pred model.predict(X_val) scores.append(mean_absolute_error(y_val, y_pred)) print(MAE 均值:, np.mean(scores), ±, np.std(scores))TimeSeriesSplit和普通KFold的区别在于它只允许用过去预测未来不会把未来的数据泄露到训练集里。这点必须写进代码注释里答辩老师一看到时间序列交叉验证加分的概率很大。4.3 可视化分析与结论呈现模型训练完成后除了输出指标还要生成图表。项目里的可视化分析通常包含两个图一个是真实温度 vs 预测温度的曲线对比另一个是残差分布图。用 matplotlib 画图时中文乱码是个大坑必须设置字体import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False # 正常显示负号 plt.figure(figsize(12, 4)) plt.plot(df_test[date], df_test[high], label真实最高温) plt.plot(df_test[date], y_pred, label预测最高温, alpha0.8) plt.legend() plt.title(测试集最高温预测对比) plt.xticks(rotation45) plt.tight_layout() plt.savefig(weather_forecast_result.png, dpi150)axes.unicode_minus设置成 False 是为了让负号正常渲染否则坐标轴上的 -1 会显示成方块。残差分布图可以画出预测误差的直方图并标注 MAE 值这也是大作业报告里“分析”部分的核心。模型本身的评估指标建议用 MAE 而不是 R²天气预测的绝对误差更直观比如 MAE1.8℃ 就代表平均偏差不到两度比“准确率 92%”这种说法更专业。5. 部署与答辩main.py 的串联和常见坑5.1 主流程编排main.py 的作用是把上面三步串起来。不要让用户手动先执行三个脚本而是通过if __name__ __main__依次调用from GetData import crawl_weather from ProcessData import process_all from GetModel import train_and_evaluate if __name__ __main__: crawl_weather() # 1. 抓取最新数据 process_all() # 2. 清洗 构造特征 切分 train_and_evaluate() # 3. 训练 评估 可视化每个函数里要加print提示当前阶段和耗时方便在控制台观察进度。项目里还有Model.pkl这个文件是训练好的模型持久化结果正式使用时不需要每次都重新训练。5.2 模型持久化与加载模型保存用joblib比pickle更快且对 numpy 数组兼容性更好import joblib joblib.dump(model, Model.pkl) # 加载 model joblib.load(Model.pkl) new_predict model.predict(X_new)注意Model.pkl是从Model.pkl这个文件读还是从GetModel.py里直接训练项目里已经提供了Model.pkl说明离线训练已完成。如果你的环境重装了包记得重新跑 GetModel.py 生成一次。5.3 答辩常问问题和调参建议常见问题回答要点对应参数为什么用随机森林不用线性回归天气与历史特征存在非线性关系随机森林能捕捉交互且不需要归一化。max_depth8缺失值为什么填充而不是删除删除会打断时间序列连续性用前向填充更合理。fillna(methodffill)测试集 MAE 比训练集高很多过拟合增大min_samples_leaf或者增加特征high_ma3平滑波动。min_samples_leaf4换城市后模型还能用吗不能直接复用因为天气模式不同需要重新训练但代码流程可复用。date_range参数改掉最后提一个部署时容易忽略的点Model.pkl里的模型是用旧版本 sklearn 训练的如果你本地是不同版本加载时会出现“sklearn version mismatch”警告。解决方法是重新运行 GetModel.py或者在保存前指定protocol版本。把请求头里的 User-Agent 换成你浏览器实际的字符串可以避免不少爬虫阶段的 403 报错。本文还有配套的精品资源点击获取