尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

LSTM气温预测实战:Python爬虫到Keras建模完整项目解析

发布时间:2026/9/23 23:40:26

资讯中心
01
ARTICLE

LSTM气温预测实战:Python爬虫到Keras建模完整项目解析

LSTM气温预测实战:Python爬虫到Keras建模完整项目解析
简介基于LSTM的气温预测及可视化项目提供完整Python源码、气温数据集与详细说明文档面向计算机相关专业毕业设计、课程设计及LSTM入门学习者覆盖气温预测从数据采集到结果展示的全流程。压缩包共16个文件8个py脚本负责爬虫、数据预处理、模型训练与可视化2个xlsx存放气温数据2个md为项目说明另有4个编译缓存文件。整体仅721KB轻量易用。项目用bs4从中国天气网抓取北京、上海、广州、郑州四城市2011年以来气温数据经滑窗构造样本后以过去5天预测未来1天温度基于Keras搭建LSTM网络并提供可视化展示训练效果与预测曲线。资源已有281人学习脚本注释与文档较完整便于复现LSTM时间序列预测流程、理解模型工作机制也可作为扩展多城市气象分析与模型调优的基础便于快速上手。1. 气温预测难在哪LSTM 是如何用 5 天数据预测第 6 天的先抛一个反直觉的结论气温预测用线性回归去拟合效果差得让人怀疑人生但换成 LSTM哪怕每个时刻只喂进去一个气温数值用过去 5 天预测第 6 天预测曲线就能稳稳咬住真实走势。这个基于 LSTM 的气温预测及可视化 Python 源码项目把整条链路做全了——用 bs4 从中国天气网爬北京、上海、广州、郑州四个城市 2011 年到近期的逐日气温做窗口切片、归一化、Keras 建模、训练评估再用 matplotlib 把真实值和预测值画在同一张图上。它不是那种只贴一段模型的 demo而是从爬虫到可视化逐文件可跑的完整工程。适合两类人一类是正在做毕设或课程设计、需要跑通一个时间序列项目的在校生另一类是想看 LSTM 在真实气温数据上到底怎么落地、边界在哪的开发者。2. 项目结构与数据链路从爬虫到训练集四个文件各管一段拿到压缩包先别急着跑把文件结构捋一遍就知道这份源码的工程划分思路了。核心文件就这几个weather_spider.py负责爬数据dataset.py负责把一维气温序列切成 LSTM 能吃的窗口config.py集中管超参数model.py建模型train.py做训练predict.py和demo.py做预测与可视化最上面还有一份README.md讲项目背景和运行步骤。文件职责关键输出weather_spider.pybs4 爬取四城市气温CSV 文件dataset.py滑动窗口切分、归一化x_train / y_train / x_test / y_testconfig.py集中管理超参数全局配置对象model.pyKeras 搭建 LSTM编译好的模型train.py训练 回调训练好的模型文件predict.py / demo.py预测 可视化对比曲线图2.1 weather_spider.pybs4 爬四个城市气温存 CSV数据是整个项目的燃料爬虫这一步决定了后面模型吃的是干净数据还是脏数据。weather_spider.py用的是requests拿页面 BeautifulSoup解析 HTML 的做法属于 Python 爬虫最经典的一套组合没有引入 Scrapy 这类重框架降低了上手门槛。# weather_spider.py # 爬取中国天气网四个城市的历史气温保存为 CSV import requests from bs4 import BeautifulSoup import csv import time BASE_URL https://example.weather.cn/city/{city_id} # 实际URL以目标页面为准 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } CITIES { beijing: 101010100, shanghai: 101020100, guangzhou: 101280101, zhengzhou: 101180101, } def fetch_city(city_name: str, city_id: str): 抓取单个城市页面并抽取温度字段 url BASE_URL.format(city_idcity_id) resp requests.get(url, headersHEADERS, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) # 页面结构变化时只需改这里的解析逻辑 rows soup.select(ul.t_clear li) # 示例选择器按实际DOM微调 records [] for row in rows: date row.select_one(.date).text.strip() temp row.select_one(.temp).text.strip() records.append((date, temp)) # 每解析一条睡一会儿避免请求频率过高 time.sleep(0.5) write_to_csv(city_name, records) def write_to_csv(city_name: str, records: list): with open(f{city_name}_temperature.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([date, temperature]) writer.writerows(records)这段代码的逻辑并不复杂先拼接 URL带一个浏览器 User-Agent 发请求然后用 BeautifulSoup 按页面结构选中每一行的日期和温度节点解析完写入 CSV。两个地方值得注意resp.encoding utf-8必须显式指定否则中文日期可能乱码time.sleep(0.5)是给目标网站留的喘息时间四个城市的数据爬下来也能把请求频率控制在一个合理区间。2.2 dataset.py把一维气温序列切成窗口构造 (5, 1) 输入LSTM 不认一维数组它要求输入是「窗口序列」。dataset.py做的事情就是把[t0, t1, t2, ..., tn]这种一维气温序列按「过去 5 天预测第 6 天」的规则切成样本对前 5 个值做特征第 6 个值做标签然后往后滑动一位继续切。# dataset.py # 将一维气温序列转换为LSTM输入格式 import numpy as np import pandas as pd WINDOW_SIZE 5 # 时间步用过去5天预测下一天 def load_series(csv_path: str) - np.ndarray: 读取CSV并返回气温数组 df pd.read_csv(csv_path) return df[temperature].values.astype(np.float32) def make_windows(series: np.ndarray, window: int WINDOW_SIZE): 滑动窗口切分前window个点做x第window1个点做y x, y [], [] for i in range(window, len(series)): x.append(series[i - window:i]) # 取前5个连续值 y.append(series[i]) # 第6个值作为标签 return np.array(x), np.array(y) def split_dataset(x: np.ndarray, y: np.ndarray, ratio: float 0.8): 按时间顺序切训练/测试集禁止随机打乱 split_idx int(len(x) * ratio) return x[:split_idx], x[split_idx:], y[:split_idx], y[split_idx:]make_windows里i - window:i这种切片写法切出来每个样本是[t(i-5), t(i-4), t(i-3), t(i-2), t(i-1)]标签就是t(i)。这里有一个时间序列特有的铁律必须遵守不能随机打乱数据再做切分。普通图像分类任务打乱样本没问题但气温数据一旦打乱前后时间依赖关系就被破坏了模型等于在看未来数据去猜过去训练出来的指标全是假的。所以split_dataset严格按时间顺序切前面 80% 做训练、后面 20% 做测试模拟的是“用历史预测未来”的真实场景。2.3 config.py把超参数集中管理改一处全局生效config.py存在的意义是把散落在各个脚本里的魔法数收拢到一个文件里。LSTM 项目超参数多——窗口大小、训练比例、批次大小、训练轮数、学习率、隐藏单元数——如果每个脚本里都写死一个数字调参时要在多个文件里来回改极易改漏。# config.py # 集中管理所有超参数训练/预测脚本统一从这里取值 WINDOW_SIZE 5 # 时间步过去5天预测下一天 TRAIN_RATIO 0.8 # 训练集占比 BATCH_SIZE 32 # 每批喂给模型的样本数 EPOCHS 100 # 完整训练轮数 LEARNING_RATE 0.001 # Adam优化器初始学习率 HIDDEN_UNITS 64 # LSTM隐藏层神经元数量 # 数据路径 RAW_DATA_DIR ./data MODEL_SAVE_PATH ./lstm_temperature.h5BATCH_SIZE 32在样本量不大时是稳妥起点显存或内存紧张可以降到 16LEARNING_RATE 0.001是 Adam 优化器的常见默认值大多数场景不用动后面避坑章节会讲学习率太大会出现什么灾难。HIDDEN_UNITS 64对单变量气温预测来说已经偏奢侈这个项目每个时间步只有 1 个特征64 个隐藏单元完全够用再往上加收益不大只会拖慢训练。2.4 数据链路整体串联从原始 HTML 到 LSTM 输入张量四个文件不是孤立的它们是一条流水线weather_spider.py把网页变成 CSVdataset.py把 CSV 读进来、归一化、切窗口、按时间拆训练集和测试集config.py给这条流水线提供统一的参数配置。数据走到这一步格式已经从「一维气温数组」变成了「(样本数, 时间步, 特征维度)」的三维张量也就是 (样本数, 5, 1)。这一步做完model.py里的 LSTM 层才能直接消费这份数据。3. LSTM 为什么适合气温时间步、特征维度和窗口长度这段讲清楚 LSTM 的原理和为什么选它以及输入格式背后每个数字的含义。理解了这一章后面调参就不是玄学而是有的放矢。3.1 为什么是 LSTM 而不是 ARIMA 或普通 RNN气温预测本质上是一个时间序列回归问题可选模型不少ARIMA 是统计学派的老兵RNN 是深度学习的先行者LSTM 是 RNN 的改良版。项目最终选了 LSTM 是有原因的。ARIMA 对序列有较强的平稳性假设气温数据带着明显的季节性周期做差分后虽然能勉强建模但遇到节假日降温、寒潮突袭这类非平稳突变ARIMA 的线性框架很难接住。普通 RNN 理论上能处理序列但反向传播时梯度要穿越多个时间步序列一长就容易梯度消失或梯度爆炸前面的信息传不到后面。LSTM 引入了门控机制——遗忘门、输入门、输出门——每个门都是一个带 sigmoid 激活的神经网络层通过“记忆细胞”这条传送带把关键信息跨时间步传递。简单说LSTM 学会了“什么时候该记住、什么时候该遗忘”这让它能捕捉气温序列中的中短期依赖昨天今天的气温趋势会影响明天而两周前的气温对明天的影响已经微乎其微这种筛选能力正是 ARIMA 和普通 RNN 不具备的。3.2 输入格式 (batch_size, timesteps, features) 到底怎么理解LSTM 在 Keras 里的输入必须是三维张量这是新手最容易卡住的地方。三维分别是batch_size、timesteps、features——batch_size是一批喂多少个样本timesteps是每个样本包含几个连续时间点features是每个时间点上用几个数值来描述状态。这个项目里timesteps5、features1所以切完窗口后还要做一次 reshape把二维的 (样本数, 5) 变成三维的 (样本数, 5, 1)。# dataset.py 补充重塑为LSTM三维输入 def reshape_for_lstm(x: np.ndarray) - np.ndarray: 将 (samples, window) 重塑为 (samples, window, 1) return x.reshape((x.shape[0], x.shape[1], 1)) # 使用方式 # x.shape 从 (952, 5) 变成 (952, 5, 1)reshape 这一步经常有同学漏掉报错信息“ValueError: Input 0 of layer lstm is incompatible with the layer”就是在提醒你维度不对。features1的意思是预测气温时每个时间点只用“气温”这一个变量如果哪天想把湿度、气压、风速也加进来做成多变量预测只需要把features改成对应的数值同时保证每个时间点的数据维度对齐。这个项目保留了单变量设计是想让新手先把主线逻辑跑通多变量扩展留作进阶练习。3.3 归一化为什么气温要缩放到 0~1 之间再进模型LSTM 内部用的是 tanh 和 sigmoid 激活函数它们的输出范围分别是 (-1, 1) 和 (0, 1)。如果原始气温数值直接灌进去——夏天 35 度、冬天零下 5 度——大数值会把激活函数推到饱和区梯度变得极其微小训练几乎走不动。归一化在这个项目里用的是 MinMaxScaler 思路把气温序列线性缩放到 0 到 1 之间公式是 (x - min) / (max - min)。# dataset.py 补充MinMax归一化 def minmax_scale(series: np.ndarray): min_val, max_val series.min(), series.max() scaled (series - min_val) / (max_val - min_val) return scaled, min_val, max_val注意一个隐蔽的坑归一化要在切窗口之前做且测试集不能用自己的 min/max 去缩放。常见错误是先把数据切成训练集和测试集然后分别算各自的 min/max 归一化这会导致测试集的数值分布被独立缩放训练时模型见过的数据范围跟测试时对不上预测结果乱套。正确做法是先用训练集的 min/max 做归一化再让测试集复用同一组 min/max 做转换。最后预测出的结果要还原成真实气温也得反过来用训练集的 min/max 做逆变换。4. 模型构建与训练Keras 搭建单层 LSTM 的完整可跑流程这一章直接落到代码把model.py、train.py、predict.py三个文件串起来讲清楚。从建模型到训练再到预测还原和出图完整跑一遍。4.1 model.pyLSTM 层的 units、return_sequences 和激活函数模型结构非常简洁就是一层 LSTM 加一层全连接输出。单变量预测任务不需要堆叠多层 LSTM一层 64 个隐藏单元已经能拟合气温这种平滑的时间序列。# model.py # 构建单层LSTM回归模型 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense import config def build_model(): model Sequential() # 输入形状 shape(timesteps, features)(5, 1) model.add(LSTM( unitsconfig.HIDDEN_UNITS, # 64个隐藏单元 input_shape(config.WINDOW_SIZE, 1), # (时间步, 特征数) return_sequencesFalse # 只返回最后一个时间步的输出 )) model.add(Dense(1)) # 输出层预测一个气温值 model.compile(optimizeradam, lossmse, metrics[mae]) return modelreturn_sequences这个参数值得展开如果设成TrueLSTM 会返回每个时间步的隐藏状态形状是 (batch_size, 5, 64)设成False只返回最后一个时间步的输出形状是 (batch_size, 64)。当前任务只要预测未来 1 天的气温只需要最后一个时间步携带的信息所以用False。只有当你搭建多层的堆叠 LSTM 或者要做序列到序列预测时前几层才需要设成True。损失函数用mse而不是mae因为 MSE 对大误差的惩罚更重能让模型更努力地去拟合极端气温。4.2 train.pyepoch、batch_size 和回调函数怎么配合训练脚本里除了model.fit之外一定要加ModelCheckpoint和EarlyStopping这两个回调。前者让模型每训练完一个 epoch 就把当前最优权重存一份到磁盘后者监测验证集 loss连续 N 轮不下降就提前停止训练——这是防止过拟合和防止断电白干的双保险。# train.py # 训练LSTM模型并保存最优权重 import numpy as np from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping import config from dataset import load_series, make_windows, split_dataset, minmax_scale, reshape_for_lstm from model import build_model # 1. 加载数据并归一化 series load_series(./data/beijing_temperature.csv) scaled, min_val, max_val minmax_scale(series) # 2. 切窗口 切训练/测试集 x, y make_windows(scaled) x_train, x_test, y_train, y_test split_dataset(x, y, config.TRAIN_RATIO) # 3. 重塑为 (samples, timesteps, features) x_train reshape_for_lstm(x_train) x_test reshape_for_lstm(x_test) # 4. 建模型 回调 model build_model() checkpoint ModelCheckpoint( config.MODEL_SAVE_PATH, monitorval_loss, save_best_onlyTrue, verbose1 ) early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) # 5. 训练 history model.fit( x_train, y_train, validation_data(x_test, y_test), epochsconfig.EPOCHS, batch_sizeconfig.BATCH_SIZE, callbacks[checkpoint, early_stop], verbose1 )patience10的意思是验证集 loss 连续 10 个 epoch 没有刷新最低记录就停。epochs100不是真的每次都要跑满有了 EarlyStopping通常 40 到 60 轮就会触发早停省下将近一半时间。batch_size32决定了每个批次梯度估计的噪声程度太小梯度震荡大、收敛慢太大会让模型陷入局部最优出不来32 是多数场景的甜点位。训练完成后history里记录了每个 epoch 的训练 loss 和验证 loss可以直接拿去做损失曲线可视化——如果训练 loss 持续下降但验证 loss 在某个点后反弹那就是过拟合的信号说明HIDDEN_UNITS太多或者patience不够。4.3 predict.py 与 demo.py把模型输出还原成真实气温预测脚本分三步加载保存的模型、拿测试集做预测、把 0~1 之间的预测值逆变换回真实温度。最容易漏的就是最后一步——直接比较归一化后的数值没有物理意义必须用训练时保存的min_val和max_val做逆变换。# predict.py # 加载模型并还原预测结果 import numpy as np from tensorflow.keras.models import load_model import config from dataset import load_series, make_windows, split_dataset, minmax_scale, reshape_for_lstm # 重新构造数据必须与训练时使用相同的min/max series load_series(./data/beijing_temperature.csv) scaled, min_val, max_val minmax_scale(series) x, y make_windows(scaled) _, x_test, _, y_test split_dataset(x, y, config.TRAIN_RATIO) x_test reshape_for_lstm(x_test) # 加载最优权重 model load_model(config.MODEL_SAVE_PATH) # 预测并还原 pred_norm model.predict(x_test) pred pred_norm * (max_val - min_val) min_val true y_test * (max_val - min_val) min_valdemo.py做的事情就是在predict.py的基础上用 matplotlib 出图。画两条曲线——真实气温和预测气温横轴是测试集天数。这份源码的图还算有讲究预测曲线会和真实曲线整体贴合但峰值的尖角部分通常会“钝化”一到两天这不是 bug是 LSTM 单步预测的固有偏差下一章专门讲这个现象。# demo.py 可视化核心部分 import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(true, label真实气温, linewidth1.5) plt.plot(pred, labelLSTM预测, linewidth1.5, linestyle--) plt.xlabel(测试集天数) plt.ylabel(气温 (°C)) plt.title(北京气温 LSTM 预测结果对比) plt.legend() plt.grid(alpha0.3) plt.savefig(prediction_result.png, dpi150)5. 避坑与常见问题跑这个项目最容易翻车的五个地方这个项目的代码我逐行看过整体写得很规矩但新手照着跑还是会踩到几个固定坑位。把这些坑按「现象 → 原因 → 解决」整理出来能省下大把调试时间。5.1 爬虫拿到空数据或字段解析为 None现象weather_spider.py跑完CSV 文件里只有表头或者 temperature 列全是空值。原因天气网站的前端结构改版导致 CSS 选择器匹配不到目标节点另一个常见问题是目标站点对无头请求做了拦截返回的 HTML 是一个验证页面而不是真实数据。soup.select返回空列表循环体里的.text自然就是空字符串。解决先手动用浏览器打开目标 URL右键检查确认页面结构是否变化再把soup.select(ul.t_clear li)换成实际存在的选择器。如果页面返回验证码或跳转给requests增加headers里的Referer和Accept-Language并适当拉长time.sleep的间隔。我一般会在解析前先打印一行print(soup.title.text)确认拿到的是不是目标页面再往下走。5.2 训练时 loss 变成 NaN现象训练日志里 loss 在某个 epoch 突然变成nan之后怎么跑都回不来。原因最常见的是学习率太大导致梯度更新步长过大损失值越过数值表达范围另一个可能是气温序列里有缺失值被读成 NaN污染了整个样本窗口。解决先检查数据pd.read_csv后调用series.isnull().sum()看有没有空值有就做向前填充或者直接删掉对应行。数据没问题就把LEARNING_RATE从 0.001 降到 0.0001。如果两种方案都试过还在爆大概率是梯度爆炸可以在model.compile里加clipvalue1.0给梯度裁剪强制限制梯度范数。5.3 预测曲线整体滞后真实曲线一天现象预测曲线和真实曲线的形状几乎一样但整体向右平移了一天仿佛在复读昨天的温度。原因这是 LSTM 单步预测的固有现象不是代码 bug。用过去 5 天预测第 6 天模型学到的最省事策略就是输出“跟前一天差不多”的数值——因为气温相邻两天高度相关这个策略能让 loss 降到很低。模型是诚实的它只是选择了一条误差最小的捷径。解决接受这个现象或者改用滚动预测策略——把每一步预测出的值拼回输入序列继续预测下一天。滚动预测的误差会累积但更接近真实业务场景因为真实世界里你没有“未来数据”可以参考。5.4 训练中断后需要从头再来现象训练跑到 80 个 epoch电脑断电或进程被杀重启后发现模型权重没有保存一切重来。原因没有配置ModelCheckpoint或者配置了但没指定save_best_onlyTrue导致每个 epoch 都覆盖写文件最后磁盘上留下的是最后一个 epoch可能是最差的一版的权重。解决沿用第 4 章的配置ModelCheckpoint设置save_best_onlyTrue它只会在验证 loss 创新低时写一次文件。这样哪怕训练中断磁盘上也永远保留历史最优权重。加载时用load_model(路径)不用重新训练就能继续预测。5.5 import keras 和 import tensorflow.keras 混用报错现象model.py里写的是from keras.models import Sequential但安装的是 TensorFlow 2.x运行时直接报ModuleNotFoundError: No module named keras或者两个 keras 版本混用导致模型结构加载失败。原因TensorFlow 2.x 之后Keras 已经被整合进tensorflow.keras独立安装的keras包是旧版本两者模型文件格式和 API 行为有差异混用会出现各种诡异错误。解决统一用from tensorflow.keras.models import Sequential和from tensorflow.keras.layers import LSTM, Dense。如果代码是从旧项目复制来的全局搜索替换import keras为from tensorflow import keras删掉pip list里独立的 keras 包只保留 TensorFlow 自带的那份。6. 让预测结果更可信滚动回测与多城市对比验证模型训练完、画出曲线只是第一步真正让这份源码有价值的是用它学会验证模型是否靠谱。最有效的验证手段是滚动回测让模型回到过去某个时间点用当时能拿到的数据做预测然后和真实气温对比。这样测出来的误差才是真实场景下的误差——因为你不可能在预测明天时用到明天之后的数据。# 滚动回测示例用训练好的模型连续预测未来10天 def rolling_predict(model, initial_sequence, days10): initial_sequence: 长度为5的归一化序列 days: 预测未来多少天 temp list(initial_sequence) predictions [] for _ in range(days): # 取最近5天reshape成(1, 5, 1)喂给模型 input_arr np.array(temp[-5:]).reshape(1, 5, 1) pred model.predict(input_arr, verbose0)[0, 0] predictions.append(pred) temp.append(pred) # 把预测结果拼回序列继续滚动 return predictions这个滚动回测脚本展示了一个关键思路模型预测出的每一个值都会作为下一步输入。第 6 天的预测误差会传染给第 7 天第 7 天又传给第 8 天所以滚动预测的天数越长误差累积越明显。对比单步预测滚动预测的结果更诚实——它能暴露模型在连续预测中是否快速偏离真实轨迹。如果你的滚动预测在第 3 天就开始严重跑偏说明模型过拟合了训练集的短期模式。多城市对比也是这个项目预留的好功能。北京、上海、广州、郑州四个城市用同一套超参数训练对比它们的测试集 MAE能看出 LSTM 对气候差异的适应能力——通常气候平稳的城市预测误差更小温差大的城市误差偏大。这种对比实验写进毕设里是很好的分析素材。再进一步可以把 5 天窗口拉长到 7 天或 14 天观察窗口长度对预测精度的影响曲线多数场景下窗口太长反而引入噪声太短则信息不足这个拐点就是你这个数据集上的最优时间步。我从这个项目里养成的一个习惯是任何时间序列模型训练之前先强制自己写下三个数字——时间步是多少、特征维度是多少、训练测试集的时间切分点在哪天。写清楚这三个问题模型的全部边界就画出来了后续所有调参和分析都围绕这三个数字展开。从那以后我每次拿到新的时序数据都先走一遍这个流程而不是急着调模型。LSTM 的门控机制再强也架不住数据链路和验证策略上的粗心希望这份源码和这些经验能帮你在气温预测这条路上少踩几个坑。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。