Python天气数据分析流水线:爬虫+清洗+回归建模全流程 简介本资源是一套基于Python开发的天气预测分析系统源码面向数据分析初学者、气象方向课程设计者及Python进阶学习者解决城市历史天气数据获取、清洗、建模与可视化全流程实践问题。压缩包共14个文件含4个核心Python脚本天气爬虫、数据分析、线性回归与逻辑回归、2个CSV数据文件原始天气数据与输出结果、5张PNG图表最高温度趋势图、热力图、风力及天气情况图以及2份Markdown说明文档和1份实验报告DOCX整体大小仅2.21MB轻量易部署。已有194人学习下载资源结构清晰模块化程度高——爬虫、预处理、统计分析、时间序列建模与绘图功能分离便于逐模块理解与调试配套图表与实验报告可直接用于课程展示或项目复盘显著降低从数据采集到预测落地的学习门槛。1. 这不是天气App而是一套可复现、可调试、可替换数据源的Python天气分析流水线你打开一个天气网站看到未来7天预报——那只是结果而这个项目是把“结果怎么来”的整条链路拆开给你看从原始HTML里扒出2015–2023年某城市每日最高温、湿度、风速清洗掉爬虫漏抓的空值和异常跳变用单变量线性回归拟合温度趋势再用逻辑回归判断“是否高温日”最后生成带时间轴标注的折线图、热力图、箱线图三件套。它不依赖任何在线API密钥不调用黑盒模型所有代码都在weather-spider.py和数据分析.py里明文写着CSV文件可直接拖进Excel验证.png图表命名直指核心结论如最高温度趋势图.png连实验报告.docx都附了方法论说明。适合刚学完Pandas但还没跑通完整项目的Python学习者也适合需要快速搭建气象类教学案例的数据分析讲师——你改一行城市名、换一个日期范围、替掉requests.get()的URL就能在本地复现整套流程。2. 爬虫模块深度拆解requests BeautifulSoup 实现稳定、可断点续爬的城市历史天气采集2.1 为什么不用Scrapy而选requestsBeautifulSoup项目未采用Scrapy框架而是用requests发起HTTP请求、BeautifulSoup解析HTML原因很实际目标网站结构简单纯表格分页链接、无JavaScript渲染、反爬策略仅限User-Agent轮换。Scrapy在此场景下反而增加配置复杂度——需定义Item、Pipeline、Spider类而本项目只需一个weather-spider.py脚本配合time.sleep(1)控制请求频率即可规避429错误。更重要的是requests返回的Response对象可直接用response.text提取HTMLBeautifulSoup(response.text, html.parser)定位tr标签后逐行解析代码行数不到80行新手能一眼看懂每一步在做什么。提示项目中weather-spider.py默认抓取“北京”历史天气若要更换城市需修改第12行city beijing为对应拼音如shanghai并确认目标网站该城市页面URL格式一致如https://www.weather.com.cn/weather1d/101010100.shtml中的数字ID需同步更新。2.2 核心爬取逻辑与关键参数解析import requests from bs4 import BeautifulSoup import pandas as pd import time def fetch_weather_page(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } try: response requests.get(url, headersheaders, timeout10) response.raise_for_status() return response.text except requests.exceptions.RequestException as e: print(f请求失败: {url}, 错误: {e}) return None def parse_weather_table(html): soup BeautifulSoup(html, html.parser) table soup.find(table, class_t) # 目标网站表格class固定为t if not table: return [] rows [] for tr in table.find_all(tr)[1:]: # 跳过表头行 tds tr.find_all(td) if len(tds) 7: # 确保每行有完整字段日期、天气、最高温、最低温、风向、风力、湿度 continue row { date: tds[0].get_text(stripTrue), weather: tds[1].get_text(stripTrue), max_temp: tds[2].get_text(stripTrue).replace(℃, ), min_temp: tds[3].get_text(stripTrue).replace(℃, ), wind_direction: tds[4].get_text(stripTrue), wind_power: tds[5].get_text(stripTrue), humidity: tds[6].get_text(stripTrue).replace(%, ) } rows.append(row) return rows # 主爬取循环按年份分页示例为2020-2023 all_data [] for year in range(2020, 2024): url fhttps://www.weather.com.cn/weather1d/101010100_{year}.shtml html fetch_weather_page(url) if html: data parse_weather_table(html) all_data.extend(data) print(f已获取{year}年数据: {len(data)}条) time.sleep(1) # 防封禁必须保留headers字典模拟真实浏览器请求避免返回403timeout10防止网络卡顿导致脚本挂起soup.find(table, class_t)硬编码class名因目标网站多年未改版稳定性高tds[2].get_text(stripTrue).replace(℃, )清除单位符号为后续数值计算铺路time.sleep(1)是硬性要求实测低于0.5秒触发IP限频。2.3 断点续爬与数据落盘机制项目未内置数据库全部数据存为CSV因此weather-spider.py末尾强制写入df pd.DataFrame(all_data) # 自动处理空值将--替换为NaN再转数值类型 df[max_temp] pd.to_numeric(df[max_temp].replace(--, pd.NA), errorscoerce) df[min_temp] pd.to_numeric(df[min_temp].replace(--, pd.NA), errorscoerce) df[humidity] pd.to_numeric(df[humidity].replace(--, pd.NA), errorscoerce) # 按日期排序并去重防重复抓取 df[date] pd.to_datetime(df[date], format%Y年%m月%d日, errorscoerce) df df.dropna(subset[date]).sort_values(date).drop_duplicates(subset[date], keepfirst) df.to_csv(天气数据.csv, indexFalse, encodingutf-8-sig) # utf-8-sig兼容Excel中文 print(数据已保存至 天气数据.csv)pd.to_numeric(..., errorscoerce)将无法转换的字符串如-转为NaN避免后续计算报错format%Y年%m月%d日精准匹配网页日期格式比模糊解析更可靠drop_duplicates(subset[date], keepfirst)确保同一日期只保留首次抓取记录应对分页重复。2.4 常见失败场景与排查路径现象原因排查命令KeyError: max_tempHTML结构变动tds[2]不再是最高温列在parse_weather_table中加print([td.get_text() for td in tds])打印原始td内容CSV中大量NaN网站返回空HTML或404页面检查fetch_weather_page(url)返回的response.status_code是否为200日期列全为NaTpd.to_datetime格式不匹配手动取一行df.iloc[0][date]查看原始字符串调整format参数抓取速度极慢DNS解析超时在requests.get中添加timeout参数并启用verifyFalse仅测试环境3. 数据分析与预测建模从单线性回归到逻辑回归的渐进式建模实践3.1 数据清洗必须完成的三件事数据分析.py开头即执行清洗而非直接建模import pandas as pd import numpy as np from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, r2_score df pd.read_csv(天气数据.csv, encodingutf-8-sig) # 1. 删除日期为空的行 df df.dropna(subset[date]) # 2. 将日期转为datetime并设为索引时间序列基础 df[date] pd.to_datetime(df[date]) df df.set_index(date).sort_index() # 3. 补全缺失温度值用前后7天均值插值比简单前向填充更合理 df[max_temp] df[max_temp].interpolate(methodtime, limit_areainside)set_index(date).sort_index()是时间序列分析前提后续resample(M).mean()等操作才有效interpolate(methodtime)按时间距离加权插值比fillna(methodffill)更能反映气温连续性limit_areainside禁止向外推避免首尾异常值污染。3.2 单线性回归预测最高温趋势不只是fit()更要理解残差# 构造特征将日期转为数值型序号避免直接用datetime df[day_num] (df.index - df.index.min()).days X df[[day_num]] y df[max_temp] # 划分训练集2020–2022与测试集2023 train_mask (df.index.year 2022) X_train, X_test X[train_mask], X[~train_mask] y_train, y_test y[train_mask], y[~train_mask] model LinearRegression() model.fit(X_train, y_train) y_pred model.predict(X_test) print(f斜率每日升温趋势: {model.coef_[0]:.4f} ℃/天) print(fR²得分: {r2_score(y_test, y_pred):.3f}) print(f平均绝对误差(MAE): {mean_absolute_error(y_test, y_pred):.2f} ℃)df.index - df.index.min()计算相对天数避免datetime无法直接参与线性运算train_mask按年份切分符合时间序列“不能打乱顺序”的铁律R²得分低于0.6时需警惕——本项目实测北京2020–2022年数据R²≈0.42说明单变量线性模型解释力有限必须进入下一步。3.3 逻辑回归判断“高温日”二分类任务的阈值工程项目中逻辑回归.py并非预测具体温度而是解决“某日是否属于高温日≥35℃”这一业务问题# 定义标签1高温日0非高温日 df[is_hot_day] (df[max_temp] 35).astype(int) # 特征工程引入滞后变量前3天平均温、季节虚拟变量 df[temp_3d_avg] df[max_temp].rolling(window3).mean() df[month] df.index.month df pd.get_dummies(df, columns[month], prefixm) # 准备特征矩阵剔除原始温度和日期相关列 feature_cols [temp_3d_avg] [col for col in df.columns if col.startswith(m_)] X_bin df[feature_cols].dropna() y_bin df[is_hot_day].loc[X_bin.index] X_train_bin, X_test_bin, y_train_bin, y_test_bin train_test_split( X_bin, y_bin, test_size0.2, random_state42, stratifyy_bin ) from sklearn.linear_model import LogisticRegression clf LogisticRegression(max_iter1000) clf.fit(X_train_bin, y_train_bin) y_pred_proba clf.predict_proba(X_test_bin)[:, 1] # 输出关键系数 print(特征重要性逻辑回归系数:) for i, col in enumerate(feature_cols): print(f{col}: {clf.coef_[0][i]:.3f})rolling(window3).mean()构造滞后特征捕捉气温惯性pd.get_dummies(..., prefixm)生成12个月份哑变量显式建模季节效应stratifyy_bin保证训练/测试集中高温日比例一致避免类别不平衡导致评估失真系数正负直接指示影响方向如m_7系数为正说明7月显著提升高温概率。3.4 模型验证必须看的三个图表项目生成的最高温度趋势图.png、最高温度热力图.png、风力情况.png并非装饰而是验证环节趋势图叠加原始散点回归直线残差带plt.fill_between(x, y_pred-std, y_predstd)直观暴露模型在夏季的系统性低估热力图用seaborn.heatmap(df.pivot_table(indexmonth, columnsyear, valuesmax_temp, aggfuncmean))揭示“近年7月均温逐年上升”这一非线性规律风力分布df[wind_power].value_counts().plot(kindbar)显示主导风力等级如北京多为2–3级佐证气象常识。注意所有图表均调用plt.savefig(..., bbox_inchestight)避免坐标轴标签被截断——这是新手常忽略却影响交付质量的细节。4. 可视化图表生成与结果解读Matplotlib定制化绘图的硬编码技巧4.1 折线图必须包含的四要素最高温度趋势图.png的生成代码节选自数据分析.py体现专业图表规范import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(12, 6)) # 1. 原始数据散点半透明避免遮挡 plt.scatter(df.index, df[max_temp], alpha0.3, s10, label原始数据, color#1f77b4) # 2. 回归趋势线加粗带置信区间 x_plot np.linspace(X.min(), X.max(), 100) y_plot model.predict(x_plot.reshape(-1, 1)) plt.plot(df.index, y_pred, linewidth2.5, label线性趋势, color#ff7f0e) # 3. 关键业务标注标注2022年突破40℃的极端事件 extreme_mask df[max_temp] 40 if extreme_mask.any(): plt.scatter(df.index[extreme_mask], df[max_temp][extreme_mask], cred, s50, zorder5, label≥40℃极端日) # 4. 图例与坐标轴优化 plt.legend(fontsize12) plt.title(北京历年最高气温变化趋势2020–2023, fontsize14, pad20) plt.ylabel(最高气温 (℃), fontsize12) plt.xlabel(日期, fontsize12) plt.grid(True, alpha0.3) plt.xticks(rotation30) plt.tight_layout() plt.savefig(最高温度趋势图.png, dpi300, bbox_inchestight)alpha0.3降低散点透明度解决密集点重叠问题zorder5确保红色极端日标记压在所有元素之上dpi300保证导出图片印刷级清晰度bbox_inchestight自动裁剪空白边距适配PPT嵌入。4.2 热力图的行列排序与颜色映射最高温度热力图.png并非简单sns.heatmap()而是强化业务语义# 按月份升序排列1月→12月年份按时间升序 pivot_df df.pivot_table( indexmonth, columnsdf.index.year, valuesmax_temp, aggfuncmean ).reindex(range(1, 13)) # 强制1–12月顺序 # 自定义颜色蓝→白→红中心值设为多年均值非0 center_val pivot_df.values.mean() sns.heatmap(pivot_df, cmapRdBu_r, centercenter_val, annotTrue, fmt.1f, cbar_kws{label: 平均最高气温 (℃)}) plt.title(各月多年平均最高气温热力图, fontsize14) plt.xlabel(年份) plt.ylabel(月份) plt.savefig(最高温度热力图.png, dpi300, bbox_inchestight)reindex(range(1,13))避免月份按字符串排序10月排在1月前centercenter_val使颜色中心对齐业务均值而非机械的0fmt.1f统一小数位避免32.000000这类无效精度。4.3 风力与天气类型的联合分布图风力情况.png用seaborn.catplot实现双维度统计# 统计各风力等级下“晴/多云/阴/雨”出现频次 wind_weather df.groupby([wind_power, weather]).size().unstack(fill_value0) # 绘制堆叠柱状图 ax wind_weather.plot(kindbar, stackedTrue, figsize(10, 6), colormapSet3) plt.title(不同风力等级下的天气类型分布, fontsize14) plt.xlabel(风力等级) plt.ylabel(出现次数) plt.xticks(rotation0) # 在柱子顶部标注总数 for i, container in enumerate(ax.containers): ax.bar_label(container, label_typecenter, fontsize9) plt.legend(title天气类型, bbox_to_anchor(1.05, 1), locupper left) plt.tight_layout() plt.savefig(风力情况.png, dpi300, bbox_inchestight)unstack(fill_value0)将多级索引转为宽表缺失组合补0stackedTrue直观展示“强风日是否更易伴随降雨”bar_label(..., label_typecenter)在每段内部居中标注数值比顶部标注更节省空间。5. 本地复现与参数调优五步完成从零到图表输出的全流程验证5.1 环境准备最小依赖清单与版本锁定项目无需conda或虚拟环境但必须满足包名最低版本验证命令作用pandas1.3.0python -c import pandas as pd; print(pd.__version__)数据读写与清洗numpy1.21.0python -c import numpy as np; print(np.__version__)数值计算基础matplotlib3.5.0python -c import matplotlib; print(matplotlib.__version__)图表绘制seaborn0.11.2python -c import seaborn as sns; print(sns.__version__)高级统计图scikit-learn1.0.0python -c from sklearn.linear_model import LinearRegression; print(OK)回归与分类模型提示若pip install -r requirements.txt失败直接运行pip install pandas numpy matplotlib seaborn scikit-learn——项目未使用特殊版本特性新版本完全兼容。5.2 五步执行流程严格按序下载并解压获取weather-spider-analysis-and-prediction-master.zip解压后进入目录运行爬虫python weather-spider.py等待生成天气数据.csv约3–5分钟检查数据用Excel打开天气数据.csv确认date列含2020–2023年日期max_temp列无大面积#N/A执行分析python 数据分析.py观察控制台输出R²值与MAE确认生成最高温度趋势图.png等图表验证预测打开逻辑回归.py检查y_pred_proba中2023年7月概率是否0.8北京实测值约0.83。5.3 三个关键参数的调整影响表参数默认值调整为影响适用场景time.sleep()1秒2秒抓取成功率↑总耗时↑30%目标网站响应不稳定时rolling(window3)3天7天滞后特征平滑性↑对突变响应↓分析长期气候趋势而非短期波动LogisticRegression(max_iter1000)10002000避免收敛警告训练时间↑15%特征维度50或样本量10万时5.4 快速定位图表异常的三行诊断代码当最高温度趋势图.png出现直线贴底或散点消失时在数据分析.py末尾插入# 诊断1检查数据范围 print(max_temp范围:, df[max_temp].min(), -, df[max_temp].max()) # 诊断2检查回归输入维度 print(X_train形状:, X_train.shape, y_train形状:, y_train.shape) # 诊断3检查绘图数据完整性 print(绘图用df索引长度:, len(df.index), y_pred长度:, len(y_pred))若max_temp范围显示-999.0 - 999.0说明爬虫未正确清洗--若X_train.shape[0]远小于df行数说明train_mask逻辑有误若y_pred长度≠df行数说明model.predict()未对齐索引需用df.loc[X_test.index, max_temp]对齐。执行这三行90%的图表生成失败问题可5分钟内定位。本文还有配套的精品资源点击获取