ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python天气数据工程全链路实战:采集、清洗、建模与可视化

Python天气数据工程全链路实战:采集、清洗、建模与可视化 简介本资源是一份面向计算机专业本科生的Python期末大作业实战项目聚焦天气数据爬取与可视化分析全流程适用于课程设计、毕业设计选题及项目能力提升场景。压缩包共24个文件含4个核心Python脚本main.py、GetData.py等、4个CSV天气数据集train/test/valid等、12张项目过程与结果截图含界面、图表、流程图、1份README.md说明文档、1个HTML静态页面及模型文件pkl等整体1.42MB结构清晰、模块分工明确。已有106人学习下载项目经导师指导并获98分高分评价所有代码均本地实测可运行配套文档详述环境配置、数据获取逻辑、清洗步骤与Matplotlib/Seaborn可视化实现要点。学习者可直接复现完整数据采集→处理→建模→可视化的闭环流程掌握requestsBeautifulSoup爬虫、Pandas数据清洗、Scikit-learn基础建模及多维度图表呈现等关键技术点。1. 这不是“爬个网页画个折线图”的玩具项目而是一套可直接用于课程答辩、毕设开题甚至实习面试的天气数据工程闭环你可能已经试过用requests BeautifulSoup抓几个城市温度再matplotlib画张图——但交作业时被助教问“为什么没处理反爬训练集和测试集怎么划分模型预测的是温度还是天气类型可视化图表能不能响应式缩放”就卡住了。这个 98 分大作业恰恰补上了这关键一环它把「网络请求→数据清洗→特征工程→模型训练→结果可视化→本地部署」全链路跑通且每一步都留有可调试入口。main.py是调度中枢GetData.py封装了带 User-Agent 轮换与重试机制的稳定采集逻辑ProcessData.py对date_train.csv等三类数据集做缺失值插补与时间序列对齐Model.pkl是用scikit-learn训练好的轻量级回归模型非简单平均而china_today.csv和wps*.jpg系列截图则证明它真正在本地 Windows/macOS 环境下完整运行过。适合计算机、信管、统计类专业学生快速复现高分作业也适合作为 Python 数据工程入门的“最小可行项目”。2. 天气数据采集模块深度解析从静态 HTML 解析到动态反爬应对策略2.1 为什么不用 SeleniumGetData.py的 requests正则组合更轻量且可控该作业未引入重量级浏览器自动化工具而是基于requests构建稳定采集层。核心原因在于目标站点天气网.html是纯静态页面所有天气数据均嵌入在 HTML 的script标签内形如script typetext/javascript var weather_data {city:北京,date:2024-03-15,temperature:12℃/2℃,weather:晴,wind:北风3-4级}; /scriptGetData.py中的关键逻辑如下import re import requests from urllib.parse import urljoin def fetch_weather_html(city_url): 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 } try: resp requests.get(city_url, headersheaders, timeout10) resp.raise_for_status() return resp.text except requests.exceptions.RequestException as e: print(f请求失败 {city_url}: {e}) return None def parse_weather_from_html(html_content): # 匹配 script 标签中 weather_data 变量赋值语句 pattern rvar\sweather_data\s*\s*({.*?}); match re.search(pattern, html_content, re.DOTALL) if not match: return None try: # 使用 ast.literal_eval 安全解析 JSON-like 字符串避免 eval import ast data_dict ast.literal_eval(match.group(1)) return { city: data_dict.get(city, ), date: data_dict.get(date, ), high_temp: int(re.search(r(\d)℃, data_dict.get(temperature, )).group(1)) if re.search(r(\d)℃, data_dict.get(temperature, )) else None, low_temp: int(re.search(r/(\d)℃, data_dict.get(temperature, )).group(1)) if re.search(r/(\d)℃, data_dict.get(temperature, )) else None, weather: data_dict.get(weather, ), wind: data_dict.get(wind, ) } except (ValueError, SyntaxError, AttributeError) as e: print(f解析 weather_data 失败: {e}) return None提示ast.literal_eval()替代eval()是本项目关键安全实践。它只允许解析基础数据结构dict/list/tuple/int/float/str/None杜绝任意代码执行风险。若目标站点改用window.__INITIAL_STATE__或 JSONP 接口只需调整正则模式和解析方式主体结构无需重写。2.2 城市列表管理与并发控制main.py中的可配置采集入口main.py并非直接硬编码城市名而是通过读取外部配置实现扩展性# main.py 片段 CITY_CONFIG [ {name: 北京, url: https://www.tianqi.com/beijing/}, {name: 上海, url: https://www.tianqi.com/shanghai/}, {name: 广州, url: https://www.tianqi.com/guangzhou/}, {name: 深圳, url: https://www.tianqi.com/shenzhen/} ] def run_data_collection(): all_records [] for city_info in CITY_CONFIG: print(f正在采集 {city_info[name]}...) html fetch_weather_html(city_info[url]) if html: parsed parse_weather_from_html(html) if parsed: parsed[source_city] city_info[name] # 显式标记来源 all_records.append(parsed) time.sleep(1.5) # 强制间隔避免高频请求触发 IP 限流 # 保存为 CSV字段对齐 df pd.DataFrame(all_records) df.to_csv(raw_weather_data.csv, indexFalse, encodingutf-8-sig) print(原始数据已保存至 raw_weather_data.csv)参数默认值说明修改建议time.sleep(1.5)1.5 秒模拟人工访问节奏若采集城市数 10建议升至 2.0若仅本地测试可注释掉CITY_CONFIG列表长度4 个控制采集范围新增城市需确保 URL 正确且页面结构一致否则parse_weather_from_html返回Noneencodingutf-8-sig启用 BOM兼容 Excel 中文显示Windows 用户务必保留否则 Excel 打开 CSV 会乱码2.3 反爬应对实操User-Agent 轮换与请求头伪造虽然本项目目标站较简单但GetData.py已预留 UA 轮换接口便于后续升级USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ] def get_random_headers(): return { User-Agent: random.choice(USER_AGENTS), Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en-US;q0.8,en;q0.7, Accept-Encoding: gzip, deflate, Connection: keep-alive, Upgrade-Insecure-Requests: 1 } # 在 fetch_weather_html 中替换 headers 赋值 # headers get_random_headers()注意当前版本未启用此轮换仍用固定 UA但代码结构已支持一键切换。若遇到403 Forbidden优先检查 UA 是否过期其次确认目标 URL 是否变更如tianqi.com改为tianqi.123.com。3. 数据清洗与特征工程从原始字符串到机器学习就绪的结构化数据集3.1ProcessData.py的三阶段清洗流水线设计ProcessData.py不是简单调用pandas.dropna()而是构建了明确的三阶段流程格式标准化 → 缺失值填充 → 时间序列对齐。其输入为raw_weather_data.csv输出为date_train.csv、date_valid.csv、date_test.csv三个文件严格遵循监督学习数据集划分规范。3.1.1 高低温温度字段的健壮提取原始temperature字段为12℃/2℃类字符串直接int()会报错。ProcessData.py使用正则预处理import pandas as pd import numpy as np import re def clean_temperature_col(df): # 提取高温第一个数字和低温斜杠后第一个数字 df[high_temp] df[temperature].str.extract(r(\d)℃).astype(float) df[low_temp] df[temperature].str.extract(r/(\d)℃).astype(float) # 处理异常值高温低于低温数据录入错误 mask df[high_temp] df[low_temp] df.loc[mask, [high_temp, low_temp]] df.loc[mask, [low_temp, high_temp]].values # 清洗后删除原始 temperature 列 df df.drop(columns[temperature]) return df3.1.2 天气类型编码与风力等级量化weather和wind是文本型字段需转换为数值特征供模型使用def encode_weather_features(df): # 天气类型映射按体感影响程度排序 weather_map { 晴: 5, 多云: 4, 阴: 3, 小雨: 2, 中雨: 1, 大雨: 0, 雷阵雨: 1, 雾: 2, 霾: 1, 沙尘暴: 0 } df[weather_code] df[weather].map(weather_map).fillna(3) # 未映射项默认为“阴” # 风力等级提取“北风3-4级” → 3.5 def parse_wind_level(wind_str): if not isinstance(wind_str, str): return 0.0 level_match re.search(r(\d)-(\d), wind_str) if level_match: return (int(level_match.group(1)) int(level_match.group(2))) / 2.0 single_match re.search(r(\d)级, wind_str) return float(single_match.group(1)) if single_match else 0.0 df[wind_level] df[wind].apply(parse_wind_level) df df.drop(columns[weather, wind]) return df3.1.3 时间序列对齐构造统一日期索引为支持后续时间序列分析ProcessData.py强制将date列转为datetime并设为索引def align_to_datetime_index(df): # 确保 date 列存在且为字符串 if date not in df.columns or df[date].dtype ! object: raise ValueError(date 列缺失或类型错误) # 标准化日期格式兼容 2024-03-15 和 2024/03/15 df[date] pd.to_datetime(df[date].str.replace(/, -), errorscoerce) # 删除无效日期行 df df.dropna(subset[date]) # 设为索引并按日期升序排列 df df.set_index(date).sort_index() # 补全缺失日期前向填充模拟连续观测 full_range pd.date_range(startdf.index.min(), enddf.index.max(), freqD) df df.reindex(full_range, methodffill) # 前向填充 return df # 主清洗函数 def process_raw_data(input_pathraw_weather_data.csv, output_dir./data): df pd.read_csv(input_path, encodingutf-8-sig) df clean_temperature_col(df) df encode_weather_features(df) df align_to_datetime_index(df) # 划分训练集70%、验证集15%、测试集15% n len(df) train_end int(0.7 * n) valid_end int(0.85 * n) train_df df.iloc[:train_end] valid_df df.iloc[train_end:valid_end] test_df df.iloc[valid_end:] # 保存 train_df.to_csv(f{output_dir}/date_train.csv, encodingutf-8-sig) valid_df.to_csv(f{output_dir}/date_valid.csv, encodingutf-8-sig) test_df.to_csv(f{output_dir}/date_test.csv, encodingutf-8-sig) print(f数据清洗完成训练集 {len(train_df)} 行验证集 {len(valid_df)} 行测试集 {len(test_df)} 行)关键参数说明freqD指定日频补全若需小时级数据改为freqH并确保原始数据含时间戳methodffill是气象数据常用策略今日天气大概率延续昨日比bfill或interpolate更符合物理规律。4. 可视化模块实战Matplotlib 与 Seaborn 混合绘图及本地 HTML 导出4.1main.py中的可视化调度逻辑与图表类型选择依据main.py的run_visualization()函数并非简单调用plt.show()而是根据数据特性选择四类图表趋势图Line Plot用于high_temp/low_temp的时间序列变化date_train.csv分布图Histogram KDE展示全国城市高温分布密度china_today.csv热力图Heatmap呈现各城市天气编码与风力等级的关联强度对比柱状图Bar Plot比较北京、上海、广州、深圳四城当日温差high_temp - low_temp核心代码如下import matplotlib.pyplot as plt import seaborn as sns import pandas as pd from datetime import datetime def run_visualization(): # 设置中文字体解决中文乱码 plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS, DejaVu Sans] plt.rcParams[axes.unicode_minus] False # 读取训练集用于趋势图 train_df pd.read_csv(./data/date_train.csv, index_coldate, parse_datesTrue) # 1. 温度趋势图 plt.figure(figsize(12, 6)) plt.plot(train_df.index, train_df[high_temp], label高温, color#E74C3C, linewidth2) plt.plot(train_df.index, train_df[low_temp], label低温, color#3498DB, linewidth2) plt.title(2024年训练集温度趋势日均, fontsize16) plt.xlabel(日期) plt.ylabel(温度 (℃)) plt.legend() plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(./output/temperature_trend.png, dpi300, bbox_inchestight) # 2. 全国城市高温分布来自 china_today.csv china_df pd.read_csv(./data/china_today.csv, encodingutf-8-sig) plt.figure(figsize(10, 6)) sns.histplot(china_df[high_temp], kdeTrue, bins20, color#2ECC71) plt.title(全国主要城市今日高温分布, fontsize14) plt.xlabel(高温 (℃)) plt.ylabel(城市数量) plt.savefig(./output/high_temp_distribution.png, dpi300, bbox_inchestight) # 3. 天气编码与风力等级热力图 corr_matrix china_df[[weather_code, wind_level]].corr() plt.figure(figsize(6, 4)) sns.heatmap(corr_matrix, annotTrue, cmapcoolwarm, center0, squareTrue, cbar_kws{shrink: .8}) plt.title(天气编码与风力等级相关性, fontsize12) plt.savefig(./output/weather_wind_corr.png, dpi300, bbox_inchestight) # 4. 四城温差对比 four_cities china_df[china_df[city].isin([北京, 上海, 广州, 深圳])].copy() four_cities[temp_diff] four_cities[high_temp] - four_cities[low_temp] plt.figure(figsize(8, 5)) bars plt.bar(four_cities[city], four_cities[temp_diff], color[#E67E22, #8E44AD, #27AE60, #2980B9]) plt.title(四大城市今日温差对比, fontsize14) plt.ylabel(温差 (℃)) for bar, diff in zip(bars, four_cities[temp_diff]): plt.text(bar.get_x() bar.get_width()/2, bar.get_height() 0.2, f{diff:.1f}℃, hacenter, vabottom) plt.savefig(./output/city_temp_diff.png, dpi300, bbox_inchestight) print(可视化图表已保存至 ./output/ 目录)重要细节plt.rcParams[font.sans-serif]必须显式设置否则 Windows 下中文标题全为方块bbox_inchestight防止保存时坐标轴标签被截断dpi300保证导出图片满足课程报告印刷要求。4.2 生成本地 HTML 报告wps*.jpg截图背后的自动化逻辑项目中的wps23.jpg等系列截图并非手动操作 WPS 截图而是由main.py调用weasyprint库将 HTML 报告转为 PDF再用pdf2image转为高清 JPG# 依赖安装pip install weasyprint pdf2image from weasyprint import HTML from pdf2image import convert_from_path import os def generate_html_report(): # 构造 HTML 内容嵌入 PNG 图表路径 html_content f !DOCTYPE html html headtitle天气数据分析报告/title stylebody {{ font-family: Microsoft YaHei; }} img {{ max-width: 100%; height: auto; }}/style /head body h1天气数据分析报告/h1 h21. 温度趋势/h2 img src./output/temperature_trend.png alt温度趋势 h22. 高温分布/h2 img src./output/high_temp_distribution.png alt高温分布 h23. 相关性分析/h2 img src./output/weather_wind_corr.png alt相关性 h24. 城市温差/h2 img src./output/city_temp_diff.png alt城市温差 /body /html # 保存 HTML with open(./output/report.html, w, encodingutf-8) as f: f.write(html_content) # 转 PDF HTML(stringhtml_content).write_pdf(./output/report.pdf) # 转 JPG每页一张高分辨率 images convert_from_path(./output/report.pdf, dpi300) for i, image in enumerate(images): image.save(f./output/wps{i17}.jpg, JPEG) # 从 wps17.jpg 开始编号 print(HTML 报告及 JPG 截图已生成)注意首次运行需安装系统级依赖。Windows 用户需下载 poppler 并将Library/bin加入 PATHmacOS 用户执行brew install popplerLinux 用户apt-get install poppler-utils。5. 模型训练与预测验证Scikit-learn 回归模型的轻量级落地实践5.1GetModel.py中的特征工程与模型选型逻辑GetModel.py并未使用复杂深度学习而是采用RandomForestRegressor—— 在小样本 1000 行、多特征天气编码、风力、日期衍生特征场景下其鲁棒性远超线性回归且训练速度极快。关键步骤包括日期特征衍生从date索引提取month、dayofweek、is_weekend等周期性特征滑动窗口特征构造过去 3 天的平均高温、温差标准差等时序特征目标变量定义预测次日高温target_high_temp df[high_temp].shift(-1)from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, r2_score import numpy as np def create_time_features(df): 从 DatetimeIndex 创建周期性特征 df df.copy() df[month] df.index.month df[day] df.index.day df[dayofweek] df.index.dayofweek df[is_weekend] (df[dayofweek] 5).astype(int) return df def create_lag_features(df, target_colhigh_temp, lags[1, 2, 3]): 创建滞后特征 for lag in lags: df[f{target_col}_lag_{lag}] df[target_col].shift(lag) # 添加滚动统计 df[f{target_col}_rolling_mean_3] df[target_col].rolling(window3).mean() df[f{target_col}_rolling_std_3] df[target_col].rolling(window3).std() return df def train_model(): # 加载清洗后数据 train_df pd.read_csv(./data/date_train.csv, index_coldate, parse_datesTrue) valid_df pd.read_csv(./data/date_valid.csv, index_coldate, parse_datesTrue) # 特征工程 train_df create_time_features(train_df) train_df create_lag_features(train_df) valid_df create_time_features(valid_df) valid_df create_lag_features(valid_df) # 定义特征列排除目标列和原始日期索引 feature_cols [col for col in train_df.columns if col not in [high_temp, low_temp]] # 构建训练集移除含 NaN 的行 X_train train_df[feature_cols].dropna() y_train train_df[high_temp].loc[X_train.index] X_valid valid_df[feature_cols].dropna() y_valid valid_df[high_temp].loc[X_valid.index] # 训练模型 model RandomForestRegressor(n_estimators100, max_depth10, random_state42) model.fit(X_train, y_train) # 验证 y_pred model.predict(X_valid) mae mean_absolute_error(y_valid, y_pred) r2 r2_score(y_valid, y_pred) print(f模型验证结果MAE {mae:.2f}℃, R² {r2:.3f}) # 保存模型 import joblib joblib.dump(model, ./Model.pkl) print(模型已保存至 Model.pkl)5.2 使用Model.pkl进行单日预测GetModel.py的 predict_single_day 函数模型训练完成后GetModel.py提供了即用型预测接口可直接传入当天特征字典import joblib import pandas as pd def predict_single_day(today_features: dict) - float: 对单日进行高温预测 today_features 示例 { weather_code: 5, wind_level: 2.5, month: 3, day: 15, dayofweek: 4, is_weekend: 0, high_temp_lag_1: 12.0, high_temp_lag_2: 11.5, high_temp_lag_3: 10.8, high_temp_rolling_mean_3: 11.43, high_temp_rolling_std_3: 0.62 } model joblib.load(./Model.pkl) # 转为 DataFrame必须与训练时列顺序一致 feature_df pd.DataFrame([today_features]) # 确保列名完全匹配 expected_cols model.feature_names_in_ missing_cols set(expected_cols) - set(feature_df.columns) if missing_cols: raise ValueError(f缺失特征列: {missing_cols}) feature_df feature_df[expected_cols] # 重排顺序 prediction model.predict(feature_df)[0] return round(prediction, 1) # 示例调用 if __name__ __main__: sample_input { weather_code: 5, wind_level: 2.0, month: 3, day: 15, dayofweek: 4, is_weekend: 0, high_temp_lag_1: 12.0, high_temp_lag_2: 11.5, high_temp_lag_3: 10.8, high_temp_rolling_mean_3: 11.43, high_temp_rolling_std_3: 0.62 } pred predict_single_day(sample_input) print(f预测明日高温{pred}℃)关键验证点model.feature_names_in_确保预测时特征顺序与训练时严格一致这是sklearn模型部署的核心安全边界。若date_test.csv中某天缺失high_temp_lag_3需先用fillna(methodffill)补全否则predict()报错。6. 本地环境一键复现指南从 Python 安装到完整流程验证6.1 最小依赖清单与版本兼容性声明本项目在以下环境完整验证通过不依赖任何云服务或远程 API全部离线运行组件版本要求验证环境说明Python≥ 3.8, ≤ 3.11CPython 3.9.18 (Windows 10), 3.10.12 (Ubuntu 22.04)不支持 3.12ast.literal_eval对某些 JSONP 字符串解析行为变更pandas≥ 1.5.01.5.3低版本reindex(methodffill)在空 DataFrame 上可能报错scikit-learn≥ 1.2.01.2.2RandomForestRegressor的feature_names_in_属性在此版本引入matplotlib≥ 3.6.03.7.1保证plt.rcParams[font.sans-serif]生效weasyprint≥ 52.057.1依赖 Cairo/PangoWindows 用户需额外安装 GTK 运行库安装命令推荐使用虚拟环境# 创建并激活虚拟环境 python -m venv weather_env source weather_env/bin/activate # Linux/macOS # weather_env\Scripts\activate # Windows # 安装核心依赖requirements.txt 内容 pip install pandas1.5.3 scikit-learn1.2.2 matplotlib3.7.1 seaborn0.12.2 pip install requests2.31.0 beautifulsoup44.12.2 pip install weasyprint57.1 pdf2image1.16.3 # Windows 用户额外安装 poppler解压后将 bin/ 加入 PATH # macOS 用户brew install poppler # Ubuntu 用户sudo apt-get install poppler-utils6.2 五步验证法确认你的本地环境 100% 可运行按顺序执行以下命令每步成功即代表对应模块就绪# 步骤 1验证数据采集生成 raw_weather_data.csv python GetData.py # ✅ 预期输出打印“正在采集北京...”最后生成 raw_weather_data.csv约 4 行 # 步骤 2验证数据清洗生成三个 CSV python ProcessData.py # ✅ 预期输出“数据清洗完成训练集 XXX 行...”./data/ 下出现 date_train.csv 等三文件 # 步骤 3验证模型训练生成 Model.pkl python GetModel.py # ✅ 预期输出“模型验证结果MAE X.XX℃, R² X.XXX”./Model.pkl 文件大小 100KB # 步骤 4验证可视化生成 ./output/ 下 PNG 和 JPG python main.py --visualize # ✅ 预期输出“可视化图表已保存至 ./output/ 目录”该目录下出现 4 张 PNG 和 10 张 JPG # 步骤 5端到端全流程等同于助教验收脚本 python main.py --full-run # ✅ 预期输出依次执行采集→清洗→训练→可视化→HTML 生成最终 ./output/report.pdf 可正常打开注意main.py支持命令行参数--full-run是隐藏功能源码中已定义但未在readme.md显式说明。这是助教实际验收时使用的指令确保所有环节无缝衔接。6.3 常见故障定位表精准匹配报错信息与解决方案报错信息精确匹配根本原因解决方案ModuleNotFoundError: No module named weasyprintweasyprint未安装或安装失败重新执行pip install weasyprintWindows 用户检查是否安装 GTK 运行库OSError: Unable to locate Ghostscript executableweasyprint依赖的 Ghostscript 缺失Windows 下下载 Ghostscript 安装后重启终端ValueError: time data 2024/03/15 does not match formatdate列格式不统一修改ProcessData.py中pd.to_datetime(..., errorscoerce)的format参数或先全局替换/为-KeyError: high_temp_lag_1GetModel.py中特征列缺失检查create_lag_features()是否被跳过确认date_train.csv中high_temp列无全 NaNUserWarning: Glyph 20320 (\N{CJK UNIFIED IDEOGRAPH-20320}) missing from font中文字体未生效在main.py开头添加plt.rcParams[font.sans-serif] [SimHei]并确认系统已安装宋体最后一行技术内容若需将本项目部署为 Web 服务可基于Flask封装predict_single_day()函数暴露/api/predictPOST 接口接收 JSON 输入并返回预测结果整个过程不超过 20 行代码。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进