news 2026/9/10 5:16:41

EMD-PCA-LSTM组合模型:光伏功率预测的Matlab实现与调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EMD-PCA-LSTM组合模型:光伏功率预测的Matlab实现与调优

做光伏功率预测的人应该都有同感:数据源看着不复杂,无非就是历史功率、辐照度、温度、湿度这几样,但真正把预测精度做上去,要过的坎一个都不少。光伏功率序列天然带强烈的非平稳性和随机波动,多云天气下功率曲线能抖成锯齿状,单靠一个模型硬啃,效果往往差强人意。

我最近重新梳理了一套在工程和学术里都很经典的组合方案——EMD-PCA-LSTM,用经验模态分解处理非平稳信号,用主成分分析压缩特征维度,再用LSTM捕捉时序依赖,整条链路在Matlab里跑通后,效果比直接用单个LSTM要稳不少。这篇文章把方案选型的原因、三个算法的角色分工、完整实操流程和排错经验都写出来,适合正在做新能源功率预测课题的研究生、搞微电网调度或光伏电站运维的工程师,以及正在折腾LSTM时间序列预测的Matlab玩家参考。

1. 这个模型到底在解决什么问题

1.1 光伏功率数据为何难预测

先看数据本身。光伏功率输出高度依赖太阳辐照度,而辐照度又受云层变化、气溶胶、温度、湿度、风速等多重因素影响,呈现明显的随机波动。晴天的时候曲线平滑,预测效果好做;多云天的时候,云层飘过导致辐照度剧烈起伏,功率曲线会在短时间内大幅震荡,这种非平稳、非线性的特性直接砸到任何预测模型脸上。

传统的时间序列方法,比如ARIMA,本质上假设序列是线性的、平稳的,遇到这种数据基本抓瞎。即便用LSTM这类深度模型,虽然能学习非线性关系,但如果输入的原始序列里混着大量高频波动和噪声,网络会把精力浪费在拟合这些随机成分上,导致泛化能力下降。所以核心思路不是让模型硬扛,而是先对序列做预处理,把复杂的信号拆解成相对简单、稳定的子成分,再分而治之。

这里有一个重要的工程认知:功率预测的误差来源不只是“模型不够强”,更多时候是“输入数据太脏、太乱、太冗余”。处理非平稳性、剔除冗余特征,往往比换一个更复杂的网络结构收益更大。

1.2 为什么是 EMD + PCA + LSTM 的组合

把三个算法放在一起,各自的角色其实是互补的:

  • **EMD(经验模态分解)**负责把原始功率序列按频率层次拆解成若干个本征模态函数(IMF)和一个残差。每个IMF代表原始信号中的一个频率分量,比原始序列平稳得多。这样LSTM面对的就是“分层递进”的子序列,而不是一团乱麻。
  • **PCA(主成分分析)**负责做特征压缩和去相关。EMD分解之后会得到多个IMF分量,直接全部塞进LSTM,不仅输入维度高、训练慢,而且相邻IMF之间可能存在相关性;同时气象特征(辐照度、温度、湿度、气压、风速、风向等)本身也常有较强的线性相关。PCA把这些特征转换为一组互不相关的主成分,只保留贡献率高的前几个,信息损失很少,但维度大幅下降。
  • **LSTM(长短期记忆网络)**负责对时序依赖建模。光伏功率在时间维度上有明显的自相关性——前一小时的天气状态和功率水平会影响后面的输出。LSTM通过门控机制,可以记住有用的历史信息、过滤掉噪声,比普通RNN更能处理长距离依赖,也比传统前馈网络更适合这种时序预测任务。

这三个模型串联起来,相当于先做“信号整理”,再做“特征提纯”,最后做“时序推理”,每一步都在为下一步降低难度。

1.3 整体流程与数据流向

整套流程可以画成一条清晰的流水线:

  1. 收集历史光伏功率数据和对应时段的气象数据,统一时间分辨率(比如15分钟一个点)。
  2. 对功率序列做EMD分解,得到若干个IMF和残差分量。
  3. 将IMF矩阵(或IMF加气象特征)做PCA降维,确定保留的主成分个数。
  4. 用PCA后的特征序列构造训练样本:取过去T个时刻的特征窗口,预测未来H个时刻的功率。
  5. 送入LSTM网络训练。
  6. 测试集上做预测,反归一化,评估RMSE、MAE、MAPE等指标。

这里有两条常见的技术路线:一条是“分解—分别建模—重构”,即对每个IMF分别训练LSTM,最后叠加预测结果;另一条是“分解—降维—统一建模”,即EMD之后用PCA压缩IMF特征,再用一个LSTM直接预测原始功率。前者精细但训练多个模型、工作量翻倍;后者实现简单、效果稳定,也是很多开源Matlab代码采用的方式。下面实操部分我主要讲后者,更符合工程落地场景。

2. 三个核心算法逐个拆开看

2.1 EMD:自适应地把信号拆成“频率层”

经验模态分解的核心思想是:任何复杂信号都可以分解成若干个本征模态函数(IMF)和一个残差。所谓IMF,要满足两个条件:一是极值点和过零点的数量相等或最多差一个;二是上下包络线关于时间轴局部对称。

EMD的分解过程是一个“筛选”(sifting)过程,大致是:

  1. 找出原始信号的局部极大值和极小值。
  2. 用三次样条插值分别拟合上包络线和下包络线。
  3. 计算上下包络线的均值。
  4. 用原信号减去包络均值,得到一个新信号。
  5. 重复以上步骤,直到新信号满足IMF条件。

这个过程会把信号中最高频的成分先剥离出来,形成IMF1,然后对剩余部分继续分离,依次得到IMF2、IMF3……直到残差变成一个单调函数或小于预设阈值。

EMD最大的优势是自适应性。和小波变换需要预设小波基函数、VMD需要预设模态数K不同,EMD完全由数据驱动,不需要提前指定基函数和分解层数,特别适合光伏功率这种没有固定规律的非平稳信号。Matlab从R2018a开始内置了emd函数,直接调用即可,不用自己手工实现筛选过程。

实际操作中我会习惯把IMF画出来观察一下。如果某个IMF呈现明显的规律性波动,说明它对应一个稳定的周期成分;如果某个IMF杂乱无章且幅度极小,可以视为噪声分量,必要时可以舍弃。这种“看图说话”的检查习惯,比只看分解指标更直观。

2.2 PCA:把相关特征压缩成主成分

主成分分析是经典的无监督降维算法,核心逻辑是:在保留数据方差最大的方向上做坐标变换,把原来可能相关的多个特征,转换为一组互不相关的线性组合,也就是主成分。

数学上,对标准化后的特征矩阵 (X),计算协方差矩阵,求出其特征值和特征向量。特征值刻画了对应主成分携带的方差大小,按从大到小排列,前几个主成分往往已经代表了绝大多数信息。通常用“累计贡献率”来决定保留几个主成分,比如保留到累计贡献率95%。

在光伏预测场景里,PCA有两个典型应用位置:

  • 对EMD分解出的IMF矩阵做PCA。如果分解出8个IMF和1个残差,共9个特征,直接输入LSTM不仅维度高,而且某些高频IMF和低频IMF之间可能存在相关性。用PCA压缩后可能只需要4-5个主成分就足以表征原序列的主要信息。
  • 对气象特征做PCA。辐照度、温度、湿度、风速、风向之间往往存在关联(比如高温天往往辐照度强、湿度低),把这些相关特征压缩为几个综合主成分,可以减少模型输入的冗余度,也能降低过拟合风险。

在Matlab里用pca函数非常方便,explained输出每个主成分的方差百分比,cumsum(explained)就能直接看累计贡献率,据此确定保留个数。

要注意的是,PCA虽然强大,但它本身假设特征是线性相关的。如果特征是强非线性关系,PCA的效果会打折扣,这时可以考虑核PCA,不过对光伏功率预测来说,线性PCA在绝大多数情况下已经够用了。

2.3 LSTM:记住过去才能预测未来

LSTM是循环神经网络(RNN)的一种改进结构,专门解决常规RNN在长序列上梯度消失、难以学会长期依赖的问题。它引入了三个门控:遗忘门决定丢弃哪些历史信息,输入门决定写入哪些新信息,输出门决定当前时刻输出什么。通过这三个门的配合,网络可以自主判断哪些历史信息重要、哪些可以遗忘。

打个生活中比方:你每天看天气决定要不要带伞,昨天的天气会影响今天的判断,但一周前某天有没有下雨就没那么重要了。LSTM的遗忘门就负责把“一周前是否下雨”这种过时信息扔掉,而输入门会记住“今早天色阴沉”这种关键信号。

光伏功率预测本质上就是一个依赖历史状态的时序问题。前几个时刻的功率水平、辐照度变化趋势,会直接影响当前时刻的输出。LSTM在训练过程中会自动学到这种时间依赖模式,比单纯的XGBoost加滞后特征做法更灵活,因为它不需要人工指定“看多长的历史窗口”——虽然实践中我们还是会设置一个滑动窗口长度,但LSTM内部对窗口内各位置的权重是学习出来的,而不是等权输入。

在Matlab的Deep Learning Toolbox里,搭建LSTM层只需要一行lstmLayer,配上前面的序列输入层、后面的全连接层和回归层,再交给trainNetwork训练,使用门槛并不高。

3. Matlab 实操:从数据到预测结果

3.1 数据准备与预处理

先说数据格式。我建议整理成一张时间表,每一行是一个时刻,包含:

  • 时间戳(用于对齐和划分)
  • 光伏功率(kW或MW)
  • 水平面总辐照度(W/m²)
  • 环境温度(°C)
  • 湿度(%)
  • 风速(m/s)
  • 其他可用的气象特征(气压、风向等,可选)

时间分辨率建议统一为15分钟或1小时。分辨率太高,数据量大、噪声多;分辨率太低,会丢失光伏功率的日内波动细节。15分钟是行业里比较常用的折中方案。

预处理有三个关键动作,任何一个偷懒都会在后续埋雷:

  1. 缺失值与异常值处理:连续型特征用前后时刻线性插值填充,功率值出现负值(可能是传感器误差)要修正为0或剔除。特别暴力的异常值(比如功率超过装机容量)建议直接标记为缺失再插值。
  2. 时段筛选:光伏功率在夜间基本为0,如果训练数据里塞了大量夜间0值,模型会倾向于“预测为0”,而白天时段的误差被平均掩盖掉。更合理的做法是:训练和评估时只保留辐照度大于某个阈值(比如10 W/m²)的白天时段;预测应用时,夜间直接输出0。这个细节对MAPE指标影响极大。
  3. 归一化:LSTM对输入尺度敏感,辐照度可能是三位数、温度可能是两位数、功率可能是四位数,不归一化会导致训练不稳定。Matlab里用mapminmax把各特征映射到[0,1]或[-1,1]区间,并把归一化参数保存下来。

数据划分也要注意:时间序列不能像普通分类那样随机打乱,必须按时间顺序划分。我习惯前70%做训练集,中间15%做验证集(用于LSTM训练过程中监控过拟合),最后15%做测试集,模拟真实“用过去预测未来”的场景。

3.2 EMD 分解:代码与图解检查

Matlab内置的emd函数调用方式非常简洁。假设功率序列存在power这个列向量中:

% power: N x 1 的功率序列 [imf, residual] = emd(power, 'Display', 0);

输出imf是一个矩阵,行数是IMF的数量,列数和原始序列长度一致;residual是残差,同样是一个行向量。比如原始序列有10000个点,分解出8个IMF,那么imf就是8×10000的矩阵,residual是1×10000的向量。

分解之后我建议立刻画图检查:

figure; subplot(6,1,1); plot(power); title('原始功率'); for i = 1:size(imf,1) subplot(6,1,i+1); plot(imf(i,:)); title(['IMF ', num2str(i)]); end

观察点主要是:IMF1到IMF n的频率是否大致由高到低排列;有没有某个IMF出现严重的端点飞翼;有没有相邻两个IMF出现相似波形(模态混叠的迹象)。如果分解结果不理想,可以考虑调整emd函数的'SiftRelativeTolerance'等参数,或者改用带噪声辅助的EEMD、CEEMDAN来处理模态混叠问题。不过先用默认参数跑一遍,大部分情况下是能看的。

有一个要点:EMD是对整个功率序列直接分解,得到的每个IMF的长度和原序列一致。这意味着分解过程用到了整段数据的信息。在学术评测中这样处理是常见的简化做法;如果将来要部署到实时系统,需要用滑动窗口EMD或在线分解策略做改造,这点我在第4节再展开。

3.3 PCA 降维:主成分个数的确定

得到IMF矩阵后,下一步做PCA。需要先把imfresidual拼起来,构成完整的特征矩阵。注意转置方向:pca函数要求行是观测样本、列是变量,所以要把IMF矩阵转置成“时刻×分量数”的形式。

% 将IMF和残差拼接为特征矩阵 features = [imf; residual]'; % N x (numIMF+1),N为时间点数 % PCA降维 [coeff, score, latent, ~, explained] = pca(features); % 计算累计贡献率 cum_contribution = cumsum(explained); % 找到累计贡献率达到95%的主成分个数 k = find(cum_contribution >= 95, 1, 'first'); fprintf('保留前 %d 个主成分,累计贡献率 %.2f%%\n', k, cum_contribution(k)); % 提取降维后的特征 features_pca = score(:, 1:k);

score是原始数据在主成分方向上的投影,也就是降维后的新特征;explained是每个主成分解释的方差百分比。通常累计贡献率取95%是一个经验阈值,具体可以看数据表现——如果前3个主成分就解释了98%的方差,那果断降到3维;如果要到第8个才到90%,说明分解出来的各分量本身相关性不高,这时可以适当放宽阈值,宁可多留一两个主成分也不要把有用信息丢掉。

如果还想把气象特征也纳入PCA,可以单独对气象特征矩阵做同样的操作,然后把两组降维结果按列拼接。不过要注意的是,如果气象特征里包含辐照度这种和功率强相关的变量,PCA之后虽然保留了大部分方差,但“方差大”不等于“预测功率有用”,PCA不看标签、不做监督筛选,这是它的固有局限。所以我在实际项目中,对气象特征的态度是:先用相关性分析过滤掉与功率几乎无关的变量(比如风向有时候相关性很弱),再对保留特征做PCA。

3.4 构建 LSTM 网络并训练

LSTM输入数据需要组织成cell数组。每个样本是一个“滑动窗口”,格式为:每个输入矩阵大小是“特征数×窗口长度”,对应的目标值是未来一个时刻(或多个时刻)的功率值。

假设窗口长度为T,特征维度为numFeatures,样本总数为numSamples

T = 24; % 用过去24个时刻(6小时,15分钟分辨率)预测未来 numFeatures = size(features_pca, 2); % PCA降维后的特征数 numResponses = 1; % 预测未来一个时刻的功率 % 构造样本 XTrain = cell(numTrain, 1); YTrain = zeros(numTrain, 1); for i = 1:numTrain XTrain{i} = features_pca(i:i+T-1, :)'; % numFeatures x T YTrain(i) = power(i+T); % 预测窗口后一个时刻的功率 end

注意这里坐标要对齐:如果第i个样本的输入是从ii+T-1,那么标签就是i+T。训练集和测试集分开构造,不能混在一起。

网络结构我建议这样搭:

layers = [ sequenceInputLayer(numFeatures) lstmLayer(64, 'OutputMode', 'last') dropoutLayer(0.2) fullyConnectedLayer(32) reluLayer fullyConnectedLayer(numResponses) regressionLayer];

解释一下各层的作用:sequenceInputLayer接收多特征时序输入;lstmLayer(64)是LSTM主体,隐含单元数设为64,OutputMode设为'last'表示只输出最后一个时间步的隐状态,适合序列到单点的预测;dropoutLayer随机丢弃一部分神经元,防止过拟合;再加一个32节点的全连接层和ReLU激活,让网络有一定非线性拟合能力;最后输出一个功率值,用回归层计算损失。

在训练选项上,需要重点关注的参数有:

options = trainingOptions('adam', ... 'MaxEpochs', 150, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 0.005, ... 'LearnRateDropFactor', 0.2, ... 'LearnRateDropPeriod', 30, ... 'ValidationData', {XVal, YVal}, ... 'ValidationFrequency', 20, ... 'Shuffle', 'every-epoch', ... 'Plots', 'training-progress', ... 'Verbose', 1);
  • InitialLearnRate:学习率不是越大越好,也不是越小越好。0.005是LSTM回归任务中比较常见的起点,损失震荡厉害就调低到0.001,收敛慢就稍微调高一点。
  • LearnRateDropPeriod:每隔多少个周期把学习率乘以0.2,让训练后期用小学习率精细收敛。
  • ValidationData:每训练20次就在验证集上评估一次,配合Plots可以直观看到训练损失和验证损失有没有分叉。

训练只要一行:

net = trainNetwork(XTrain, YTrain, layers, options);

训练过程中重点观察验证集损失:如果验证损失开始上升而训练损失持续下降,基本就是过拟合了,可以加大dropout比例或增加训练数据。

3.5 预测、反归一化与误差评估

测试集预测和训练时一样构造窗口样本,然后调用predict

YPred = predict(net, XTest);

注意predict返回的可能是cell或数值数组,取决于网络设置,通常用cell2mat转换一下。预测出来的是归一化后的功率值,需要反归一化:

YPred_raw = mapminmax('reverse', YPred, ps_power);

其中ps_power是第3.1节对功率做归一化时保存的结构体。这里有个很容易踩的坑:如果归一化是在EMD分解之前对整个功率序列做的,反归一化直接用这个参数没问题;但如果对每个IMF分别做了归一化,那就必须分别反归一化再叠加,千万别混用参数。

评估指标建议至少算四个:

% 计算误差指标 RMSE = sqrt(mean((YPred_raw - YTest_raw).^2)); MAE = mean(abs(YPred_raw - YTest_raw)); MAPE = mean(abs((YPred_raw - YTest_raw) ./ YTest_raw)) * 100; SS_res = sum((YTest_raw - YPred_raw).^2); SS_tot = sum((YTest_raw - mean(YTest_raw)).^2); R2 = 1 - SS_res / SS_tot;

MAPE要特别注意:如果测试集里包含夜间或功率接近0的点,分母接近0会导致MAPE爆炸。所以MAPE应该只在功率值大于某阈值(比如额定功率的5%)的时刻上计算,这在工程上叫“有效时段误差评估”。RMSE和MAE则没有这个限制。

可视化一般画三张图:预测值与真实值对比曲线、误差分布直方图、散点图(横轴真实值、纵轴预测值,越贴近对角线越好)。这些图在论文里也是标配,面试或汇报时也能直观展示模型的可靠性。

4. 高频问题与排错复盘

4.1 问题速查表

我在跑这个流程的过程中,把踩过的坑整理成了一张速查表,很多问题在你第一次跑通时大概率也会遇到:

问题现象可能原因解决方案
训练损失不下降学习率太大或太小、数据未归一化检查归一化,把学习率调到0.001-0.01区间
验证损失远高于训练损失过拟合增大dropout、增加数据量、减小隐含单元数
EMD分解后某个IMF端点严重飞翼端点效应对序列两端做延拓(镜像延拓或多项式延拓)后再分解,分解后截掉延拓部分
相邻IMF波形相似模态混叠改用EEMD或CEEMDAN做分解
PCA累计贡献率到99%才过95%各分量本身相关性较低不必死守95%,根据验证集效果选择主成分个数
预测曲线比真实值平滑很多LSTM学习到了低频趋势,高频波动丢失结合多个IMF的输出做叠加预测,或引入辐照度作为辅助输入
MAPE为无穷大测试集含有接近0的功率点只在功率>阈值的时段计算MAPE

其中端点效应和模态混叠是EMD在工程落地中最常被问到的两个点。端点效应是因为分解时包络拟合在序列两端缺乏极端点约束,导致包络线“飞”了,常用做法是进行端点延拓后再分解。模态混叠表现为不同IMF中出现了相近频率的成分,常用EEMD(集合经验模态分解)通过在信号中加入有限幅度白噪声再多次平均来抑制混叠。

4.2 参数调优的个人经验

参数调优这块没有银弹,但有几条我实测下来的原则,可以帮你少走弯路:

先固定数据侧,再调模型侧。很多同学一上来就调LSTM层数、隐含单元数,却发现效果怎么也上不去。其实在这个组合模型里,数据侧的预处理影响往往远大于模型结构。建议先把EMD分解质量、PCA主成分个数、窗口长度这些数据侧参数固定好,再去微调LSTM结构。我遇到过把窗口长度从6小时改成12小时之后,RMSE直接下降8%的情况,比调任何LSTM参数都管用。

隐含单元数宁可略大,也要配好dropout。64个隐含单元对中小规模光伏数据来说是个稳妥起点。如果数据量大、特征多,可以加到128;但一定要同步加上dropout层,否则验证集损失会开始飘。别一开始就堆两三层LSTM,单层在很多任务上已经足够,层数增加带来的收益通常有限,训练时间和过拟合风险却成倍增加。

学习率调度比手动改学习率更重要。LearnRateDropPeriod做周期衰减,效果比固定学习率稳很多。前期大学习率快速下降,后期小学习率精细收敛,这是Adam优化器在LSTM训练里的标准打法。

对比实验必须做。别只跑EMD-PCA-LSTM一条链路,至少要和纯LSTM、EMD-LSTM(不做PCA)做对比,才知道每个模块到底贡献了多少。很多论文里说“组合模型效果更好”,你自己跑一遍才能体会到是“分解的功劳”还是“降维的功劳”,这对后续优化方向很有指导意义。

生产环境要考虑数据泄漏。前面提到,整段序列做EMD再划分训练集和测试集,在学术评测里是常见做法,但严格来说,分解时用到了未来信息。如果项目要求的是可部署的实时预测,就要改成滑动窗口在线分解,或者用“只使用历史数据重新分解”的策略来评估真实性能。这个点如果被审稿人或技术评审问到,能答上来会加分不少。

另外还有一个实用建议:训练完模型后,把归一化参数、PCA的coeff矩阵、EMD分解参数都保存成.mat文件。预测阶段加载模型和这些参数,对新的输入数据按同样的流程处理——先标准化、再分解、再投影到PCA主成分空间、最后送入LSTM。这样整套流程才真正具备复现和部署价值。

4.3 后续扩展方向

这套EMD-PCA-LSTM框架的扩展空间也很大。如果数据量充足,可以把LSTM换成更强大的时序模型,比如Transformer或TCN,前端的EMD+PCA预处理思路依然有效;如果希望进一步提升EMD分解质量,可以用CEEMDAN或者VMD替代标准EMD,代价是引入额外参数(比如VMD的K值);如果想让模型输出带置信区间,可以在LSTM后面接一个分位数回归层,这样不仅能预测功率值,还能给出预测区间,对电网调度来说实用价值更高。

最后再分享一个实际操作中的小技巧:在Matlab里调试这个流程时,别一次性把所有样本都送进训练。先用一小段数据(比如1000个样本)、极少的迭代次数跑通整个代码链路,确认维度、格式、归一化反归一化都正确,再放完整数据集训练。我曾经因为PCA之后score的维度没搞对,导致LSTM输入格式一直报错,调了大半天才发现是样本转置方向错了。先用小数据快速验证框架,能帮你省下大量Debug时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 5:13:31

Flipper Zero BadUSB快速上手:5个步骤如何验证你的USB输入防御

Flipper Zero BadUSB快速上手:5个步骤如何验证你的USB输入防御 【免费下载链接】Flipper Playground (and dump) of stuff I make or modify for the Flipper Zero 项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper 当你想验证自己实验室里那台终端…

作者头像 李华
网站建设 2026/9/10 5:10:34

Codex本地AI编码系统部署实战:从CentOS环境配置到企业级集成

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

作者头像 李华
网站建设 2026/9/10 5:10:19

阿里开源Qwen-Agent实战:从工具调用到多智能体编排

“阿里开源了一个神级Agent项目”,这句话最近在好几个技术群里反复出现。点进去一看,说的是阿里开源的 Qwen-Agent——一个基于通义千问模型体系的智能体开发框架。我花了两天时间把它从部署到实战完整跑了一遍,这篇文章就围绕这个项目展开&a…

作者头像 李华
网站建设 2026/9/10 5:08:11

Python程序员必备Linux命令:从开发到部署的实战指南

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

作者头像 李华