基于RNN、LSTM与GRU的气象数据预测实战:Python代码解析与避坑指南
简介这份资源围绕循环神经网络RNN、LSTM、GRU在气象数据预测中的应用展开面向本科、硕士阶段从事神经网络预测方向学习与教研的读者也适合希望用Python动手实践时序建模的开发者。压缩包共13个文件约758KB包含5个Python脚本、5张结果图、1份CSV气象数据、1份说明文档和1个模型权重文件脚本覆盖数据处理、模型配置、训练与测试等环节图片则记录了不同学习率与迭代次数下的预测效果对比。资源已积累501人学习说明其在时序预测入门与实验复现方面具有一定参考价值。读者可借助完整代码与数据理解LSTM等结构如何对气象序列建模并通过多组超参数结果图观察训练过程与预测精度的变化为课程设计、论文实验或算法对比提供可运行的实践素材。1. 从一份气象预测代码包说起RNN、LSTM、GRU 到底怎么选去年帮一个做农业气象服务的团队看代码他们用 LSTM 预测未来 24 小时气温结果训练 loss 降到 0.01上线后 MAE 却飙到 4.2℃。翻完代码发现他们把tq.csv里的时间列当成了普通特征喂进去模型学到的是第几行而不是几点钟。这个坑在气象时序预测里极其常见也是我拿到这份基于循环神经网络(RNN, LSTM或GRU)实现气象数据预测附python代码.zip时最先检查的地方。这份资源的核心价值在于它把 RNN、LSTM、GRU 三种循环网络结构放在同一套气象数据管道里做对比实验附带了LSTM.py、data.py、config.py、running.py、test.py五个可执行脚本以及lstm_model.pt权重文件和六张不同超参组合下的训练结果图Res1_M1_lr0.001_eps10.png到Res5_M2_lr0.005_rps6.png。数据文件tq.csv是典型的气象时序表weather_prediction目录下应该还有配套的预处理产物。适合谁如果你正在做气温、湿度、风速这类单变量或多变量时序预测的课程设计、论文实验或者想快速跑通一个 LSTM 基线模型这份代码能省掉你搭数据管道和调参框架的时间。但如果你指望直接拿它上生产环境得先看完后面的避坑章节。2. 拆开代码包数据管道与模型定义的关键实现2.1 气象数据怎么喂进 LSTMdata.py的滑动窗口逻辑气象时序预测的本质是把连续观测值切成输入窗口 → 预测目标的监督学习样本。data.py里通常做三件事读tq.csv、归一化、构造滑动窗口。我按常见实现还原一下核心逻辑你对照包里的代码看是否一致import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def load_weather_data(csv_path, seq_len24, pred_len1): df pd.read_csv(csv_path) # 假设 tq.csv 包含 time, temp, humidity, pressure 等列 # 关键时间列必须解析成 datetime 并设为索引不能当数值特征 df[time] pd.to_datetime(df[time]) df df.sort_values(time).set_index(time) features df[[temp, humidity, pressure]].values scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(features) X, y [], [] for i in range(len(scaled) - seq_len - pred_len 1): X.append(scaled[i:iseq_len]) y.append(scaled[iseq_len:iseq_lenpred_len, 0]) # 只预测 temp return np.array(X), np.array(y), scaler这段代码有三个参数决定模型上限seq_len24表示用过去 24 个时间步预测下一步对应逐小时数据就是用过去一天预测下一小时pred_len1是单步预测改成 6 就是预测未来 6 小时feature_range(0,1)是 MinMax 归一化气象数据里温度和湿度的量纲差异大不归一化会导致梯度爆炸。注意y只取了第 0 列temp如果你要同时预测多个变量得把pred_len那行改成取所有列。config.py里一般集中管理这些超参常见字段包括SEQ_LEN、PRED_LEN、BATCH_SIZE、EPOCHS、LR、HIDDEN_SIZE、NUM_LAYERS、MODEL_TYPE。从结果图命名Res1_M1_lr0.001_eps10.png能反推出实验矩阵M1/M2 可能代表两种模型结构比如 LSTM vs GRUlr 是学习率eps 是 epoch 数rps 可能是 random seed 或 steps per epoch。这种命名习惯在科研实验里很实用一眼能看出超参组合。2.2 LSTM 模型定义LSTM.py里的层数与隐藏维度LSTM.py是模型主体。PyTorch 实现 LSTM 预测网络的标准写法如下import torch import torch.nn as nn class WeatherLSTM(nn.Module): def __init__(self, input_size3, hidden_size64, num_layers2, output_size1): super(WeatherLSTM, self).__init__() self.hidden_size hidden_size self.num_layers num_layers self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropout0.2 if num_layers 1 else 0 ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x shape: (batch, seq_len, input_size) h0 torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) c0 torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) out, _ self.lstm(x, (h0, c0)) # 取最后一个时间步的输出做预测 out self.fc(out[:, -1, :]) return outinput_size3对应 temp、humidity、pressure 三个特征hidden_size64是 LSTM 单元内部状态维度太小欠拟合太大过拟合且训练慢气象数据一般 32~128 够用num_layers2是堆叠两层 LSTM第二层能学到更高阶的时序模式但超过 3 层在中小规模气象数据上收益递减dropout0.2只在多层时生效防止过拟合。batch_firstTrue让输入维度是(batch, seq_len, features)这是 PyTorch 里最容易搞混的地方——默认是(seq_len, batch, features)不设这个参数会导致训练时维度对不上。如果你想换成 GRU只需把nn.LSTM替换成nn.GRU同时去掉c0的初始化因为 GRU 只有隐藏状态没有细胞状态。RNN 则更简单nn.RNN只有h0。三种结构的参数量对比RNN 最少GRU 约为 LSTM 的 3/4LSTM 最多。在气象数据这种中等长度序列上LSTM 和 GRU 表现通常接近GRU 训练稍快。2.3 训练与验证running.py和test.py的分工running.py负责训练循环test.py负责加载lstm_model.pt做推理。训练部分的核心是损失函数和优化器选择import torch.optim as optim criterion nn.MSELoss() # 回归任务用 MSE optimizer optim.Adam(model.parameters(), lr0.001) for epoch in range(EPOCHS): model.train() for batch_x, batch_y in train_loader: optimizer.zero_grad() pred model(batch_x) loss criterion(pred, batch_y) loss.backward() # 梯度裁剪防止 LSTM 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step()MSELoss是回归标准选择如果你更关注相对误差可以换L1Loss或SmoothL1Loss。Adam配合lr0.001是安全起点从结果图看作者试过lr0.05Res2_M1_lr0.05_eps20.png这个学习率对 LSTM 偏大loss 曲线大概率震荡。clip_grad_norm_是 LSTM 训练的后悔药——气象序列长、梯度容易爆炸不加这行 loss 可能突然变 NaN。test.py里要注意的是推理时必须用训练时保存的scaler做反归一化否则输出的预测值永远在 0~1 之间。常见做法是在data.py里把 scaler 存成 pickletest.py加载后调scaler.inverse_transform。另外model.eval()和torch.no_grad()必须加上否则 dropout 和 batch norm 会干扰推理结果。3. 跑通实验从环境配置到结果复现的完整步骤3.1 环境依赖与 Python 版本选择这份代码包基于 PyTorchPython 版本建议 3.8 或 3.9热词里python 3.8出现频率高不是没道理很多深度学习库对 3.10 支持还不稳定。核心依赖pip install torch1.13.1 numpy pandas scikit-learn matplotlib如果你用 GPU 训练去 PyTorch 官网按 CUDA 版本选对应安装命令。CPU 也能跑这份气象数据规模不大tq.csv通常几千到几万行CPU 训练几分钟到十几分钟能出结果。matplotlib用于复现那六张结果图scikit-learn提供MinMaxScaler。常见翻车点pip install torch默认装 CPU 版如果你有 GPU 但没指定版本训练速度差 10 倍以上。另外 Windows 下num_workers设大于 0 可能报错设成 0 最稳。3.2 数据准备tq.csv的格式要求与检查清单tq.csv是这份代码的数据入口。跑之前先确认几件事检查项要求不满足的后果时间列可解析为 datetime格式统一排序错乱滑动窗口切出错误样本缺失值无 NaN 或已插值填充归一化报错或模型输出 NaN采样间隔等间隔逐小时/逐日LSTM 学不到真实周期模式列名与data.py中features列表一致KeyError 或静默取错列数据量至少seq_len * 10行训练集太小验证 loss 不收敛我一般会先跑一段检查脚本df pd.read_csv(tq.csv) print(df.shape) print(df.isnull().sum()) print(df[time].diff().value_counts().head()) # 看采样间隔是否一致如果diff()出现多种间隔说明数据有断档需要重采样或插值。气象数据常见断档是设备离线几小时直接df.interpolate()线性插值最省事但会引入虚假平滑论文里要说明。3.3 训练参数怎么调从结果图反推超参影响包里六张结果图对应不同超参组合我按命名规律还原一下实验设计Res1_M1_lr0.001_eps10.pngLSTM学习率 0.001训练 10 轮。这是基线loss 下降平稳但可能未完全收敛。Res2_M1_lr0.05_eps20.png学习率跳到 0.0520 轮。预期 loss 震荡剧烈验证集可能发散。res3_M1_lr0.01_eps6.png学习率 0.01只训 6 轮。欠拟合loss 还在下降就停了。res4_M2_lr0.01_eps6.png换模型结构可能是 GRU同样 6 轮。对比 M1 看收敛速度差异。Res5_M2_lr0.005_rps6.png学习率 0.005rps6可能是每轮 6 个随机种子取平均降低方差。调参优先级学习率 隐藏层维度 层数 序列长度。气象数据上lr0.001~0.005、hidden_size64~128、num_layers1~2、seq_len24~72是比较稳的区间。batch_size一般 32 或 64太小梯度噪声大太大泛化差。3.4 结果验证MAE、RMSE 与可视化对比test.py跑完后除了看 loss必须算 MAE 和 RMSE 并反归一化到原始量纲from sklearn.metrics import mean_absolute_error, mean_squared_error model.eval() with torch.no_grad(): preds model(X_test_tensor).cpu().numpy() # 反归一化只对 temp 列操作 preds_inv scaler.inverse_transform( np.concatenate([preds, np.zeros((len(preds), 2))], axis1) )[:, 0] true_inv scaler.inverse_transform( np.concatenate([y_test, np.zeros((len(y_test), 2))], axis1) )[:, 0] mae mean_absolute_error(true_inv, preds_inv) rmse np.sqrt(mean_squared_error(true_inv, preds_inv)) print(fMAE: {mae:.2f}℃, RMSE: {rmse:.2f}℃)气温预测的合理 MAE 在 1~2℃ 之间如果超过 3℃ 说明模型没学到有效模式。可视化时把真实值和预测值画在同一张图上重点看峰值和拐点是否跟上——LSTM 常见问题是预测曲线比真实曲线滞后一步这是滑动窗口单步预测的固有缺陷改成多步预测或加注意力机制能缓解。4. 避坑与排查气象时序预测里最容易翻车的五个地方4.1 现象训练 loss 正常下降验证 loss 从第 3 轮开始飙升原因模型过拟合。气象数据如果只有一年左右训练集和验证集分布差异大LSTM 参数量又远大于数据量。解决加 dropout0.2~0.3、减小hidden_size、加 L2 正则optimizer Adam(model.parameters(), lr0.001, weight_decay1e-4)、早停验证 loss 连续 5 轮不降就停。如果数据量少于 5000 行num_layers设 1 层就够。4.2 现象预测值几乎是一条直线不随输入变化原因归一化后目标值方差太小或者模型退化成只输出均值。常见于tq.csv里温度变化平缓的月份。解决检查scaler.inverse_transform后的预测值范围如果始终在均值附近说明模型没学到时序依赖。把seq_len从 24 加大到 72或者换 GRU 试试GRU 对短序列更敏感。另外确认y取的是 temp 列而不是其他变化小的列。4.3 现象RuntimeError: Expected hidden[0] size (2, 64, 64), got (2, 32, 64)原因batch_size在最后一个 batch 不整除LSTM 初始隐藏状态维度对不上。解决h0和c0用x.size(0)动态获取 batch 大小不要硬编码。或者在DataLoader里设drop_lastTrue丢弃最后一个不完整 batch。这个报错在换 batch size 后必现属于血泪经验。4.4 现象tq.csv读取后中文列名乱码原因CSV 编码是 GBK 而 pandas 默认 UTF-8。解决pd.read_csv(tq.csv, encodinggbk)或encodingutf-8-sig。如果列名本身是中文建议在data.py里重命名成英文再喂模型避免后续 matplotlib 画图时字体报错。4.5 现象GPU 显存够但训练速度极慢原因数据加载是瓶颈num_workers0时主进程串行读数据。解决Linux 下设num_workers4、pin_memoryTrueWindows 下num_workers保持 0 但把数据预加载成 tensor 放内存。另外确认model.to(device)和batch_x.to(device)都执行了只转模型不转数据等于白搭。5. 进阶技巧用 GRU 替换 LSTM 并做多步预测的实操跑通基线后最有价值的改动是把单步预测改成多步预测同时对比 GRU 和 LSTM 的推理速度。多步预测有两种策略直接多输出output_sizepred_len和自回归滚动用预测值当下一步输入。前者训练稳定但长程精度差后者精度高但误差累积。我一般先用直接多输出跑基线。改LSTM.py的输出层class WeatherGRU(nn.Module): def __init__(self, input_size3, hidden_size64, num_layers2, output_size6): super(WeatherGRU, self).__init__() self.gru nn.GRU(input_size, hidden_size, num_layers, batch_firstTrue, dropout0.2) self.fc nn.Linear(hidden_size, output_size) # 一次输出未来6步 def forward(self, x): out, _ self.gru(x) # GRU 不需要 c0 return self.fc(out[:, -1, :])output_size6对应预测未来 6 小时气温。训练时y的pred_len也要改成 6data.py里y.append(scaled[iseq_len:iseq_lenpred_len, 0])这行不用动它已经支持多步。损失函数仍用 MSE但评估时要分别算每一步的 MAE通常第 1 步最准第 6 步误差翻倍。GRU 相比 LSTM 的优势在推理速度同样hidden_size64、num_layers2GRU 参数量约为 LSTM 的 75%CPU 推理快 20%~30%。精度上气象数据这种中等长度序列两者差距通常在 5% 以内。如果你要部署到边缘设备GRU 是更务实的选择。验证多步预测效果时我习惯画一张预测步数 vs MAE的曲线step_mae [] for step in range(6): mae mean_absolute_error(true_inv[:, step], preds_inv[:, step]) step_mae.append(mae) plt.plot(range(1, 7), step_mae, markero) plt.xlabel(预测步数 (小时)) plt.ylabel(MAE (℃)) plt.savefig(step_mae.png)如果第 3 步之后 MAE 快速上升说明模型长程依赖没学好可以加一层注意力或改用 Transformer。但在气象预测这个场景里LSTM/GRU 加多步直接输出已经能覆盖大部分课程设计和论文实验的需求。从那以后我每次拿到时序预测代码包都强制先跑一遍df[time].diff().value_counts()确认采样间隔再检查scaler有没有在推理时反归一化。这两个地方翻车过太多次希望帮到你。本文还有配套的精品资源点击获取

相关新闻

从生成工具到创作智能体:H3 开源背后,MiniMax 在下一盘什么棋

从生成工具到创作智能体:H3 开源背后,MiniMax 在下一盘什么棋

从生成工具到创作智能体:H3 开源背后,MiniMax 在下一盘什么棋 【免费下载链接】Minimax-H3-ComfyUI 项目地址: https://ai.gitcode.com/hf_mirrors/Alissonerdx/Minimax-H3-ComfyUI 2026 年 8 月 3 日,MiniMax 正式开源新一代通用视频…

2026/10/10 21:39:28 阅读更多 →
Flickr30k跨模态搜索实战:双塔模型与对比学习课程设计

Flickr30k跨模态搜索实战:双塔模型与对比学习课程设计

简介:本资源为基于Flickr30k数据集的图像—文本跨模态搜索Python项目,面向计算机、人工智能、通信工程等专业的在校学生与教师,可用于媒体计算实践作业、课程设计或毕业设计。项目围绕跨模态检索任务,提供从数据划分、图像预处理到…

2026/10/10 21:39:27 阅读更多 →
基于深度学习的锂电池SOH评估:Python实战与避坑指南

基于深度学习的锂电池SOH评估:Python实战与避坑指南

简介:这份资源围绕锂电池健康状态(SOH)评估展开,采用深度学习方法对NASA锂电池容量衰退数据集进行建模,并进一步分析引入运行可监测数据后对SOH预测效果的影响。内容适合计算机、人工智能、电子信息、数学等相关专业学…

2026/10/10 21:39:27 阅读更多 →

最新新闻

2026亲测有效:6款降AI工具盘点,教你如何彻底降低AIGC机器痕迹

2026亲测有效:6款降AI工具盘点,教你如何彻底降低AIGC机器痕迹

辛苦熬夜码字大半个月,查重绿了,结果AIGC检测直接爆表飘红,连自己逐字敲的段落都被判定为AI生成,那种无奈真让人抓狂。 盲目替换同义词不仅改得语无伦次,降ai效果也微乎其微。为解决这痛点,我花半个月测遍市…

2026/10/10 22:25:12 阅读更多 →
OpenSkill 论文逐段拆解:Agent 靠“技能自进化“刷出多项 SOTA,原理并不玄

OpenSkill 论文逐段拆解:Agent 靠“技能自进化“刷出多项 SOTA,原理并不玄

OpenSkill 论文逐段拆解:Agent 靠"技能自进化"刷出多项 SOTA,原理并不玄 【免费下载链接】skills Skills Catalog for Codex 项目地址: https://gitcode.com/GitHub_Trending/skills4/skills 2026 年的开源生态里,"Ski…

2026/10/10 22:25:12 阅读更多 →
三步给 Worktrunk 接上 CI 网关:让 Agent 每次改动都先过测试再合入主干

三步给 Worktrunk 接上 CI 网关:让 Agent 每次改动都先过测试再合入主干

三步给 Worktrunk 接上 CI 网关:让 Agent 每次改动都先过测试再合入主干 【免费下载链接】worktrunk Worktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows 项目地址: https://gitcode.com/GitHub_Trending/wo/worktrunk …

2026/10/10 22:25:12 阅读更多 →
Fine语言窗口组件详解:从声明式语法到工程实践

Fine语言窗口组件详解:从声明式语法到工程实践

如果你做过GUI开发,一定知道“窗口组件”这四个字在不同的语言和框架里含义差得很远。有的语言里它指一个系统级窗口句柄,有的框架里它指一套可拖拽的控件库,而在Fine语言里,“窗口组件”是一个完整的界面描述体系——从顶层窗口到…

2026/10/10 22:25:11 阅读更多 →
基于SpringBoot的高校课程智能选课系统-附源码

基于SpringBoot的高校课程智能选课系统-附源码

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/10/10 22:24:11 阅读更多 →
多层内支撑基坑的ABAQUS模拟:从地应力平衡到收敛调试

多层内支撑基坑的ABAQUS模拟:从地应力平衡到收敛调试

从基坑开挖模拟入手,很多刚接触ABAQUS的朋友第一反应是:不就是把土挖掉吗?模型建好,边界设好,跑一下就出来了。可一旦真做起来,尤其是碰到多层内支撑的工况,问题一个接一个往外冒:地…

2026/10/10 22:23:11 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →