简介:这是一份基于Python的天气预测分析系统源码,面向数据分析、Python爬虫与机器学习初学者,覆盖从城市历史天气获取、数据清洗、建模预测到图表绘制的完整项目链路。压缩包共14个文件、大小2.21MB,包含4个Python功能脚本、2个CSV数据文件、5张可视化结果图,以及实验报告和项目说明文档,便于对照源码理解每一步实现。源码中“天气爬虫”负责自动采集天气数据,“数据分析”脚本完成预处理与探索性分析,同时提供逻辑回归、单线性回归等预测模型,能输出最高温度趋势图、热力图和风力情况对比图,直观展示历史变化与预测结果。此外,文档部分还配有实验报告和README说明,适合作为课程设计、毕业设计或自学练手的参考资料。目前已有194人学习下载,内容贴近真实业务场景,可帮助读者掌握网络爬虫、Pandas数据处理、时间序列分析和数据可视化等关键技能。
1. 天气预测分析系统:一场从爬虫到可视化图表的完整链路
如果你下载过 weather-spider-analysis-and-prediction-master 这套基于 Python 实现的天气预测分析系统源码,大概率不是只看某个孤立的函数,而是想找一个能把「抓取城市历史天气 → 数据清洗 → 回归预测 → 绘图展示」完整跑通的最小闭环。这个项目恰好就是干这个的:用 Python 爬虫获取天气数据,落到 CSV 交给 Pandas 处理,再用单线性回归和逻辑回归分别做数值预测和分类预测,最后用 Matplotlib 输出趋势图、热力图和风力图。适合刚学完 Python 语法想拿真实项目练手的人,也适合正在做数据分析课程设计、需要一套能出图能出数的交付物的读者。
2. 先看清项目结构:六个脚本各管一段,别一上来就硬跑
拆这种源码包,我习惯先把文件布局捋清楚。这个包里既有脚本又有输出物,比如天气爬虫.py、数据分析.py、单线性回归分析.py、逻辑回归.py,以及一批已经生成的 PNG 图表和 CSV 数据。每一个文件都不是摆设。
2.1 文件布局与职责划分
刚解压时很多人都想直接跑主文件,但这个项目没有传统意义的 main.py 入口,它的流程是分散在多个脚本里的。先弄清楚每个文件的职责,后面的复现顺序就顺了。
| 天气爬虫.py | 抓取城市历史天气数据 | 天气数据.csv |
| 数据分析.py | 读取 CSV,做探索性分析和预处理 | 最高温度趋势图.png、最高温度热力图.png、天气情况.png、风力情况.png |
| 单线性回归分析.py | 用日期序号预测最高温度 | Output.csv、预测对比图 |
| 逻辑回归.py | 将天气问题转化为分类问题建模 | 分类评估结果 |
| 实验报告.docx | 记录实验过程与结论 | 文档 |
从文件命名就能看出作者的模块化思路:爬虫、分析、回归、分类是四个独立环节。我在实际复现时发现,正确的执行顺序应该是先跑天气爬虫.py 生成原始数据,再跑数据分析.py 看数据长什么样,最后再碰两个模型脚本。数据都没备好就调模型,只会得到一堆报错。
2.2 天气爬虫:requests 加 BeautifulSoup 的抓取思路
天气爬虫.py 负责把城市历史天气抓下来。这种抓取任务,常见的做法是用 requests 请求目标页面,再用 BeautifulSoup 解析 HTML。下面是我按项目场景整理的抓取框架。
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
city_url = "https://example-weather-site.com/city/hangzhou" # 示意地址,按实际站点替换
resp = requests.get(city_url, headers=headers, timeout=10)
resp.encoding = "utf-8"
soup = BeautifulSoup(resp.text, "html.parser")
rows = []
for item in soup.select(".weather-row"): # 选择器以目标页面实际结构为准
date = item.select_one(".date").text.strip()
high = item.select_one(".high").text.replace("℃", "").strip()
low = item.select_one(".low").text.replace("℃", "").strip()
weather = item.select_one(".weather").text.strip()
wind = item.select_one(".wind").text.strip()
rows.append([date, high, low, weather, wind])
time.sleep(1) # 控制请求频率,降低被封风险
df = pd.DataFrame(rows, columns=["日期", "最高温度", "最低温度", "天气", "风力"])
df.to_csv("天气数据.csv", index=False, encoding="utf-8-sig")
这段代码的逻辑很直白:先带 UA 头发请求,避免部分站点直接拒绝空 UA 的客户端;拿到 HTML 后用 BeautifulSoup 定位天气信息的 DOM 节点,逐个字段提取文本;每抓一条睡一秒,给目标服务器留缓冲。关键参数里,timeout 控制单次请求超时时间,time.sleep 的间隔决定抓取频率——对天气这种低频更新站点,一秒的间隔已经足够友好。注意 weather-row 这类选择器只是示意,实际要用浏览器开发者工具确认目标站点的 class 名称。
2.3 CSV 落盘与 Pandas 读取的衔接
爬虫写完数据后,后续分析靠的是 CSV 文件,所以落盘格式直接影响后面数据处理是否顺手。这里有两个细节值得注意。
df.to_csv("天气数据.csv", index=False, encoding="utf-8-sig")
第一个细节是 index=False。如果不关掉索引,读回来时会多一列 Unnamed: 0,每次都得 drop,纯属给自己找麻烦。第二个细节是 utf-8-sig 编码。用 UTF-8 直接写文件,Python 自己读没问题,但用 Excel 打开就会乱码,因为 Windows 下的 Excel 默认按 GBK 系列解码。utf-8-sig 会带 BOM 头,Excel 能正确识别,这是天气数据项目里最容易踩的编码坑之一。
读取端我一般这样处理。
df = pd.read_csv("天气数据.csv", parse_dates=["日期"])
df["最高温度"] = pd.to_numeric(df["最高温度"], errors="coerce")
df = df.dropna(subset=["最高温度"])
parse_dates 把日期列转成 datetime 类型,后面按时间排序、画趋势图都依赖这个类型。pd.to_numeric 配合 errors="coerce" 把无法转换的脏值变成 NaN,再由 dropna 统一清理。这套组合能挡掉大部分数据质量问题。
3. 数据分析与预处理:EDA 阶段最容易漏掉的三个观察点
数据落到 CSV 只是第一步,接下来数据分析.py 做的事情,本质上是在回答「这批天气数据到底长什么样、能不能直接拿去喂模型」。我拆完这套脚本后,发现几个值得细品的操作。
3.1 先看数据的基本盘:shape、describe 和 info
很多初学者拿到数据就急着建模,结果模型指标稀烂,回头才发现是数据本身有问题。正确的第一步永远是看基本盘。
import pandas as pd
df = pd.read_csv("天气数据.csv", parse_dates=["日期"])
print(df.shape)
print(df.info())
print(df.describe())
df.shape 告诉你数据量级——几百条和几千条的建模策略完全不同。df.info() 暴露每一列的类型和缺失值数量,比如最高温度列如果是 object 类型,说明里面有非数字脏值。df.describe() 给出数值列的均值、标准差、最小最大值,最高温度波动范围是否合理,一眼就能判断。我发现看 describe 还有一个隐性收益:如果 min 和 max 出现极端值,比如夏天出现零下温度,基本可以断定原始数据源或解析环节存在问题,后面的模型再精致也没意义。
3.2 缺失值、重复值和异常值的处理顺序
天气爬虫抓回来的数据十有八九不干净,缺字段是常态。处理顺序我建议固定为:先去重,再处理缺失,最后查异常。
df = df.drop_duplicates(subset=["日期"])
df = df.sort_values("日期").reset_index(drop=True)
df["最高温度"] = df["最高温度"].interpolate()
# 异常值检测:超过 3 倍标准差的点标记为可疑
mean = df["最高温度"].mean()
std = df["最高温度"].std()
df["异常标记"] = (abs(df["最高温度"] – mean) > 3 * std).astype(int)
先按日期去重是因为同一个城市同一天的天气理论上只有一条记录,重复说明爬虫翻页时出现了重复抓取,直接丢弃即可。缺失值用 interpolate 线性插值而非 dropna,是因为温度序列是连续渐变的时间序列,前一天 20 度、后一天 24 度,中间缺失那天用插值补成 22 度,比粗暴删掉整行更合理。异常值检测用 3 倍标准差做初筛,标记出来后再人工确认,避免直接把极端高温日当成脏数据清掉。
3.3 热力图和趋势图到底在验证什么
项目里最高温度热力图看起来挺唬人,其实它的作用是快速观察温度在「日期 × 年份」两个维度上的分布规律。如果数据覆盖多年,把日期转换成周序号和年份,用透视表聚合后画热力图,能直观看到每年的高温集中在哪几周,低温又出现在哪几周。
import matplotlib.pyplot as plt
import seaborn as sns
df["年份"] = df["日期"].dt.year
df["周序号"] = df["日期"].dt.isocalendar().week.astype(int)
pivot = df.pivot_table(values="最高温度", index="年份", columns="周序号", aggfunc="mean")
sns.heatmap(pivot, cmap="coolwarm", annot=False)
plt.title("各年份周最高温度分布")
plt.savefig("最高温度热力图.png", dpi=150, bbox_inches="tight")
pivot_table 把每一年每个周序号的最高温度均值摊成矩阵,海蓝色代表低温、暖红色代表高温,哪年热得早哪年冷得晚一目了然。这个图的价值在于验证数据的时间规律性:如果热力图完全看不出季节交替的色块变化,那这批数据要么是爬错了城市,要么是时间字段解析错位了,根本走不到建模那一步。趋势图也是同理,它画的是最高温度随时间的整体走向,用来确认序列有没有明显的年度周期。
4. 预测模型:单线性回归与逻辑回归的分工比想象中更明确
项目带了两套模型脚本,很多人困惑到底该用哪个。这里态度要明确:它们解决的从来不是同一个问题,一个是回归,一个是分类。
4.1 单线性回归:拿日期序号预测最高温度,只能当基线
单线性回归分析.py 做的事情,是把日期转换成一个数值序号,再去拟合最高温度。这样做的合理假设是:如果温度存在稳步上升或下降的趋势,那序号和温度之间天然存在线性关系。比如城市建设导致的热岛效应让年均气温逐年抬升,在这种场景下单线性回归能捕捉到那条上升线。
import pandas as pd
from sklearn.linear_model import LinearRegression
df = pd.read_csv("天气数据.csv", parse_dates=["日期"])
df = df.sort_values("日期").reset_index(drop=True)
df["序号"] = range(len(df))
X = df[["序号"]].values
y = df["最高温度"].values
model = LinearRegression()
model.fit(X, y)
print(f"斜率: {model.coef_[0]:.4f}, 截距: {model.intercept_:.4f}")
线性回归的核心输出就是斜率和截距,斜率代表日期每往后推一天,最高温度平均上升或下降多少度。如果斜率接近 0,说明周期内没有明显的单边趋势,模型拟合出来的就是一条平线。要注意的是,这个模型只能预测「长期趋势」,它不知道一年有四季,所以拿它预测下一周的温度偏差会很大,这是它的能力边界,不是 bug。当基线可用,但拿来做短期预报,就属于用错工具了。
4.2 逻辑回归:把天气问题转成二分类问题
逻辑回归.py 在这个项目里的定位,就不是预测具体温度了,而是判断某天是否满足某个天气条件。最常用的转化方式是把连续温度映射成类别,比如定义「最高温度是否高于 20 度」或者「是否下雨」。只有完成这个转化,逻辑回归才有用武之地。
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
df = pd.read_csv("天气数据.csv", parse_dates=["日期"])
df["月份"] = df["日期"].dt.month
df["是否高温"] = (df["最高温度"] > 25).astype(int)
features = ["月份", "最低温度", "风力等级"]
X = df[features].fillna(df[features].mean())
y = df["是否高温"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
model = LogisticRegression(max_iter=1000)
model.fit(X_train_scaled, y_train)
print(f"准确率: {model.score(X_test_scaled, y_test):.2f}")
这段代码的关键点有两个。第一是标签构造,是否高温这个二值标签完全由业务定义,阈值定多少由你说了算,25 度只是示例,你可以按城市纬度调整。第二是特征标准化,StandardScaler 把月份、最低温度、风力等级这些量纲完全不同的特征统一到同一尺度,因为逻辑回归的梯度下降对特征尺度敏感,不标准化容易出现收敛慢甚至不收敛的情况。random_state=42 固定了训练测试集的切分方式,保证每次复现结果一致。max_iter=1000 是经验值,逻辑回归默认的 100 次迭代在这个场景下经常不够用,跑出来会带警告。
4.3 预测结果落盘:Output.csv 里到底该存什么
项目里的 Output.csv 承担的是预测结果与真实值的对比展示。回归模型的落盘内容我一般设计成三列:日期、真实最高温度、预测最高温度,再加一列误差绝对值。这样方便后续分析到底是哪些日子的预测偏差最大,是节假日突变还是天气系统异常造成的。
df_result = pd.DataFrame({
"日期": X_test["日期"],
"真实值": y_test,
"预测值": model_pred,
})
df_result["误差绝对值"] = abs(df_result["真实值"] – df_result["预测值"])
df_result.to_csv("Output.csv", index=False, encoding="utf-8-sig")
预测结果落盘的价值在于留痕——过两周你再回头看这批预测靠不靠谱,得知道当初的误差分布长什么样。不要只存一个准确率数字,把明细存下来才能复盘。模型评估也不能只看准确率,如果样本不平衡,比如 90% 的日子都是非高温,那模型全预测非高温也有 90% 准确率,但毫无实际价值。这时候要看混淆矩阵,看它到底暴露了哪类样本的预测漏洞。
5. 天气数据项目避坑指南:五个高频翻车点与排查思路
这套源码我在本地跑了不下三遍,每次翻车原因都不太一样。这一章把最常见的五类问题按「现象、原因、解决」拆开写,省得你走弯路。
5.1 爬虫一跑就被目标站拒绝
现象:requests 发出去返回 403 或者直接超时,代码反复重试也无效。原因:多数站点对无 UA 头的请求或者高频访问有识别机制,同一个 IP 短时间大量请求容易触发封禁策略。解决:先带上完整的 User-Agent 头,把 time.sleep 的间隔拉长到 1 到 2 秒;如果仍然失败,检查是否被要求登录或者页面改为动态渲染。动态渲染的页面 requests 拿不到数据,要考虑改用 Selenium 或直接找该站点提供的开放数据接口。我一般会把请求封装成带重试的函数,连续失败三次就退出并记录日志,避免死循环。
5.2 Excel 打开 CSV 全是乱码
现象:天气数据.csv 用 Python 读没问题,双击用 Excel 打开却出现一堆乱码字符。原因:to_csv 默认用 UTF-8 编码写文件,而 Windows 环境的 Excel 默认按本地代码页解码,导致中文乱码。解决:写文件时指定 encoding="utf-8-sig",这个参数会给文件加 BOM 头,Excel 识别 BOM 后自动切换解码方式。这个坑坑了我很久,在那之后我对所有产出 CSV 的脚本一律强制写 utf-8-sig,不管是给谁看。
5.3 Matplotlib 出图中文全部变成方块
现象:图表标题、坐标轴标签里的中文全部渲染成小方块,英文和数字正常。原因:Matplotlib 默认字体是 DejaVu Sans,不包含中文字形。解决:在绘图前显式指定中文字体,Windows 下一般是 SimHei 或 Microsoft YaHei:
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
第二行的 axes.unicode_minus 用来解决负号显示异常的问题。这个配置要在所有绘图代码之前执行,不然还是会用默认字体。如果换成 Linux 服务器跑,SimHei 不存在,改用文泉驿微米黑或者安装中文字体,不然出图照样挂。
5.4 逻辑回归训练时警告不收敛
现象:LogisticRegression 跑完输出一行 ConvergenceWarning,模型返回的准确率也不稳定。原因:默认 max_iter=100 对标准化不充分的特征或者复杂特征组合来说太少,梯度下降在迭代上限前没找到最优解。解决:把 max_iter 调大到 500 或 1000,同时配合 StandardScaler 做特征标准化。两者交叉验证下来,训练稳定度提升明显。如果你发现 max_iter 调到 5000 还在警告,那大概率是特征构造有问题,比如包含了大量冗余特征,这时候应该做特征筛选。
5.5 模型预测结果全是同一个值
现象:模型跑通了,但预测结果几乎是同一个类别或同一个数值,比如逻辑回归全预测 0,回归模型输出一条平线。原因:样本标签严重不平衡或者特征与目标完全没有相关性。解决:先看 y 的分布,如果正负样本比超过 9:1,就要考虑欠采样、过采样或者换评估指标;再看特征与目标的散点图,如果完全看不出关系,说明当前特征解释不了目标变量。这个问题的本质是建模方向的错误,而不是调参能解决的。
6. 进阶用法:把四个脚本串成带参数的自动化任务链
源码包里四个脚本分开跑没问题,但如果你想换城市、换时间范围重新做一遍,就得来回改文件路径和变量,非常容易漏改。我做的第一件事就是写一个 run_all.py 把它们串起来。
6.1 主控脚本的任务编排结构
import subprocess
import sys
def run_script(script_name, args=None):
cmd = [sys.executable, script_name] + (args or [])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"[失败] {script_name}")
print(result.stderr)
sys.exit(1)
print(f"[完成] {script_name}")
if __name__ == "__main__":
run_script("天气爬虫.py", ["–city", "hangzhou"])
run_script("数据分析.py")
run_script("单线性回归分析.py")
run_script("逻辑回归.py")
这种编排方式的好处是每个子脚本仍然可以独立运行,主控脚本只负责按顺序调用并检查退出码。subprocess.run 捕获子进程输出,哪一步挂掉直接在日志里看到,不用一个个脚本手动试错。注意 capture_output 会吞掉实时输出,调试时可以临时去掉,改成直接输出到终端。保存日志是为后续排查留证据,我一般把 stdout 同时写入文件。
6.2 用 argparse 把城市和日期范围参数化
要让任务链真正好用,得允许外部传入城市名和抓取日期范围,而不是每次改脚本源码。天气爬虫.py 里加上参数解析是标准做法:
import argparse
parser = argparse.ArgumentParser(description="抓取城市历史天气")
parser.add_argument("–city", default="hangzhou", help="城市拼音,如 hangzhou")
parser.add_argument("–start", default="2023-01-01", help="开始日期 YYYY-MM-DD")
parser.add_argument("–end", default="2023-12-31", help="结束日期 YYYY-MM-DD")
args = parser.parse_args()
# 请求 URL 中用 args.city 拼地址,日期范围用于循环翻页
参数化的好处是显而易见的:换城市只需改命令行参数,实验的可复现性和可追溯性都比直接改代码强得多。城市拼音、时间区间和模型输出路径都从命令行注入,脚本本身保持只读状态,这才是工程上干活的姿势。
6.3 验证新增城市的预测可信度
跑完一个城市不能直接说模型可用,至少要检查两点:一是趋势图是否符合地理常识,比如杭州的全年最高温曲线应该在七八月见顶,如果图形完全倒挂,大概率是数据源串了城市;二是 Output.csv 的误差绝对值分布,如果预测值和真实值差 10 度以上,说明回归模型根本没有抓住该城市的气候规律,这时候检查特征里是否缺少季节因子,或者需要引入月度哑变量做增强。
我现在的习惯是每套数据跑完,强制让脚本生成一份简短的数据质量报告,包含记录数、日期范围、最高温度缺失比例和预测误差分布。这套天气源码本身不复杂,直接跑一遍能拿到图、数据和预测结果,但你把它当模板去改自己的场景时,上述避坑经验才是真正让你少熬两个通宵的部分。希望帮到你。
本文还有配套的精品资源,点击获取



