新闻详情

新闻详情

首页 / 资讯中心 / 详情

用LSTM生成MIDI音乐:从数据预处理到采样解码

发布时间:2026/9/29 19:34:41来源:尧图网络
用LSTM生成MIDI音乐:从数据预处理到采样解码
简介面向深度学习音乐生成的实际项目代码基于TensorFlow/Keras构建神经网络结合music21与pygame完成MIDI数据的解析与回放可用于训练模型自动生成旋律和多轨音乐。该项目源自硕士论文“具有深度概率模型的音乐完成”压缩包内包含四个Python脚本分别负责超参数搜索、数据预处理、模型训练和音乐生成覆盖从原始MIDI数据集到最终作品输出的完整流水线。其中数据预处理模块将MIDI文件转换为适合模型输入的numpy数组训练模块支持神经网络的训练与保存生成模块可加载已有模型产出新的音乐片段超参数搜索模块则帮助用户找到更优的训练配置。压缩包为zip格式大小约1.02MB文件类型以Python脚本为主整体结构按数据处理、训练、推理分支组织便于阅读和二次开发。目前已有187人学习浏览适合具备一定深度学习基础、希望将神经网络应用于音乐生成的学生或研究者也可作为毕业设计或课程项目的完整实现模板。1. midiGenerator 是做什么的让深度神经网络学会写 Midi 乐谱很多人看到 midiGenerator 这个项目名第一反应是“让 AI 作曲”但实际落地时真正有用的思路是把它当成一个「预测下一个音符」的自回归生成任务从大量现成的 Midi 文件里把音符、力度、时长抽出来变成一串离散事件再用深度神经网络去学习这些事件的出现规律。训练完成后用一段种子输入就能滚出一段新的 Midi拿到 DAW 或打谱软件里直接播放。这套方案的价值在于绕开了音频波形生成里采样率、对齐、音色合成一长串麻烦只处理纯符号信息数据量小、可解释性强、可控性好。适合正在做音乐生成、作曲辅助工具或者想快速验证“序列模型在音乐数据上能走多远”的从业者不需要懂乐理只需要会跑 Python。2. 把 Midi 变成神经网络能学的东西事件序列与预处理2.1 为什么 Midi 是深度神经网络音乐生成最干净的入口Midi 和音频不一样它不是记录了波形而是记录了一串离散的控制事件什么时候按下哪个音符、力度多大、什么时候松开。这意味着它天然就是结构化符号不需要像音频那样对齐帧、提取特征或处理音色合成。对深度神经网络来说输入越规整模型越好学Midi 恰好能把一首曲子的所有信息压缩成一条事件列表每条事件就几个数字。常见做法是把 note_on / note_off / delta_time 抽出来按时间顺序排成一个 token 序列然后靠 LSTM 或 Transformer 去学序列分布。这套做法的训练成本比音频生成低一到两个量级一张普通消费级显卡就能玩起来这也是 midiGenerator 这类项目普遍被用作入门方向的原因。不过我见过不少人在第一步就翻车直接把 mido 读出来的原始 Midi 消息丢给模型。Midi 里除了音符还有 sustain pedal、program change、pitch bend 这类控制器消息它们会变成大量无规律的 token把模型的注意力全吸走。所以预处理的核心不只是“把 Midi 变成能训练的序列”还要把不相关的轨道和消息滤掉。我一般只保留指定 channel 上的 note_on 和 note_off其他消息全部丢弃这样事件序列才干净。2.2 用 mido 把 Midi 读成事件序列mido 是 Python 生态里读写 Midi 最常用的库示例代码如下import mido from mido import MidiFile def midi_to_events(path, channel0, drum_channel9, skip_drumsTrue): midi MidiFile(path) ticks_per_beat midi.ticks_per_beat events [] for track in midi.tracks: for msg in track: # 跳过打击乐轨通常 channel 9 是鼓组结构跟旋律差别很大 if skip_drums and msg.channel drum_channel: continue if msg.type note_on and msg.velocity 0 and msg.channel channel: events.append((NOTE_ON, msg.time, msg.note, msg.velocity)) elif msg.type note_off or (msg.type note_on and msg.velocity 0): if msg.channel channel: events.append((NOTE_OFF, msg.time, msg.note, 0)) # 统一按时间排序避免某些 midi 文件里轨道顺序错乱 events.sort(keylambda e: e[1]) return events, ticks_per_beat这段代码做了三件事按 channel 过滤只拿旋律轨把 note_on 和 note_off 都保留下来按 delta time 排序。注意 mido 里 note_on 的速度为 0 时其实等价于 note_off这个在读取时必须合并处理否则会留下永远按住的音。排序那一步不是可选项我在实践中遇到过一些 Midi 文件的轨道内部时间戳本来就是乱序的不排会导致整个训练序列的时间逻辑崩溃。channel 参数默认取 0因为多数 midi 文件的旋律在主轨但我见过不少素材旋律放在 channel 3 甚至 5建议批量预处理前先打印几份文件的轨道分布确认主旋律在哪个 channel。2.3 事件表示与 Piano Roll 的取舍把 Midi 喂给神经网络业界主要有两种表示Piano Roll 和事件序列。Piano Roll 把音符画在一个时间 x 音高的网格上维度是 time_steps × 88每个格子代表某个时刻是否有某个音符。这种表示很直观可以直接用 CNN 或 VAE 来建模但它的时间分辨率是固定的要么取样率太高让序列过长要么取样率太低导致短音符失真。事件序列则把每件事当成一个 token比如 NOTE_ON_60 / NOTE_OFF_60 / TIME_STEP模型直接按顺序学习。下面这个表是我给团队选型时的对比依据。表示方式时间表达序列长度模型适合度还原 Midi 的难度Piano Roll固定栅格较长受栅格粒度限制CNN、VAE 较顺手需要反量化短音符容易丢事件序列delta time token紧凑随音符数线性LSTM、Transformer 顺手直接转回 note_on/off我选事件序列的原因很实际它对音符时值的表达是精确的不会因为栅格化把十六分音符或三连音磨平。代价则是模型要额外学习 TIME_STEP 这种非音符 token 的规律但这个代价在训练中完全可接受。另外一个更精细的变体是把 note_on 和 note_off 合并成 note_on duration即一个音符开始后隔一个固定步长就结束省去单独建模 note_off 的开销但那种做法在复调场景里不太好用多个音符同时开始同时结束的概率不高。通常第一个项目建议直接上 NOTE_ON / NOTE_OFF / TIME_STEP 三分类事件。2.4 预处理中的关键参数时间分辨率、音符范围、速度过滤预处理参数直接影响生成质量这里说三个我会在每次跑数据前确认的点。第一时间分辨率。Midi 的 ticks_per_beat 在不同文件里很不一致常见的 480、960 甚至 120 都有。不做统一量化会带来一个隐蔽问题同一个四分音符在 480 tick 的文件里被编码成 480 个 TIME_STEP在 120 tick 的文件里只占 120 个模型会学到两种完全不同的时值分布。常见做法是把所有文件按 opset 的最大 ticks_per_beat 量化或者把所有 delta time 映射到一个固定范围的整数区间比如分成 32 个桶。我一般用 480短音符的损失在可接受范围。第二音符范围。钢琴 MIDI note 范围是 21~108但许多网络上抓下来的 Midi 文件里会混入 0~127 的全部音高其中有些是低音鼓映射或者噪音。如果把 128 个音高全部建模模型要学习的类别数会从不到 90 涨到 128而且数据稀疏实际生成的音高分布会很散。我通常直接 clip 到 21~108再偏移成 0~87这样类别数可控也符合听感范围。第三力度过滤。力度从 1 到 127如果每个力度都作为一个独立特征事件类别会翻好几倍。常见做法是把力度量化成 4 档或 8 档比如 1~32、33~64、65~96、97~127。对生成任务来说四档力度足够表达强弱对比却能让模型少学一堆无效细节。力度为 0 的 note_on 已经在上一步被当成 note_off 了这一步的力度量化只影响真正演奏的音符。3. 模型选型与训练超参LSTM 作为第一个能跑通的深度神经网络3.1 为什么先选 LSTM而不是 Transformer 或 GANmidiGenerator 标题里只说了“深度神经网络”没限定具体结构那就先选最容易复现的基线。在音乐事件序列生成这个任务里LSTM 是我最推荐的第一个模型理由有三个。第一自回归生成任务和序列模型天然匹配给定历史 token 预测下一个 tokenLSTM 的隐状态正好能携带长距离依赖。第二LSTM 对数据量要求低几百首 Midi 就能训出一个能听出旋律感的模型而 Transformer 往往需要上千首才能把注意力模式稳定下来GAN 在这种离散 token 输出上的训练稳定性又差一些。第三推理开销小生成 1000 个 token 在 CPU 上也就几秒方便反复试参数。但这不是说 Transformer 不能用。如果数据量上万首、想要更好的全局结构感Transformer 值得替换。选型逻辑可以按一个粗略标准走数据规模在几千首以下先用 LSTM几千首以上再考虑 Transformer。GAN 在 Midi 生成里一直没有成为主流因为离散 token 的采样不可导需要 Gumbel-Softmax 这类技巧训练稳定性远不如自回归似然目标。所以先跑通 LSTM后续再升级结构是投入产出比最高的路径。3.2 LSTM 事件模型 PyTorch 实现下面的代码是核心模型定义import torch import torch.nn as nn class MidiLSTM(nn.Module): def __init__(self, vocab_size, embed_size256, hidden_size512, num_layers2, dropout0.3): super().__init__() self.vocab_size vocab_size # 把事件 id 映射成向量进入 LSTM 学习时序规律 self.embedding nn.Embedding(vocab_size, embed_size) self.lstm nn.LSTM(embed_size, hidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout) # 输出层把隐状态映射回 vocab 大小的 logits self.fc nn.Linear(hidden_size, vocab_size) def forward(self, x, hiddenNone): x self.embedding(x) # (B, T, embed) out, hidden self.lstm(x, hidden) # (B, T, hidden) logits self.fc(out) # (B, T, vocab) return logits, hidden模型的输入是一批事件序列 token形状是 (batch, seq_len)输出是每个时刻对下一个事件的 logits 分布。Embedding 层先把离散事件 id 变成向量LSTM 层学习上下文最后的线性层把隐状态映射回事件类别空间。这里有个细节vocab_size 是事件词的个数也就是 NOTE_ON、NOTE_OFF、TIME_STEP 及其参数组合的数量通常在 100~200 之间视觉上会感觉比语言模型小很多但模型对序列结构的依赖反而更强。3.3 训练循环与超参数设置模型定义好以后训练循环需要注意错位标签和梯度裁剪def train_one_epoch(model, loader, optimizer, device): model.train() total_loss 0 for x, y in loader: x, y x.to(device), y.to(device) optimizer.zero_grad() logits, _ model(x) # 输入是 x[0..T-1]标签是 x[1..T]做的是 next-token 预测 loss torch.nn.functional.cross_entropy( logits.reshape(-1, logits.size(-1)), y.reshape(-1) ) loss.backward() # 把梯度模长限制在 5 以内防止 LSTM 在长序列上梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step() total_loss loss.item() return total_loss / len(loader)训练时每个样本就是一条固定长度的滑动窗口窗口里的前 T 个 token 作为输入后移一位的 T 个 token 作为标签。这里的关键是窗口长度 block_len。它不能太短否则模型看不到跨小节的重复动机也不能太长否则 GPU 显存吃紧。经验值在 128~256 之间。优化器用 Adam初始学习率 1e-3batch_size 64dropout 0.3。如果发现训练 loss 明显震荡先把学习率降到 3e-4大多数情况下会比调任何花哨的调度器都管用。梯度裁剪在 LSTM 里务必保留这个任务序列动不动几百步小批量内隐状态累积的梯度往往会远超普通 NLP 任务的尺度。3.4 数据切分与内存边界预处理时我踩过一个隐性坑把同一首 Midi 的不同切片同时分进训练集和验证集。音乐是强结构化的同一首歌的动机重复度非常高如果训练的切片里已经包含验证切片里的某个旋律验证 loss 就会虚低看起来模型很厉害换新曲子立刻露馅。正确做法是按文件粒度切分把文件列表打乱90% 的文件进训练10% 进验证同一个文件的所有切片必须归属同一侧。内存边界上LSTM 的显存占用主要由 block_len × hidden_size × batch_size 决定。单卡 8GB 显存时hidden_size 512、两层 LSTM、block_len 128、batch_size 64 基本到顶。如果 OOM不要急着换大显存卡先把 block_len 降到 64、hidden_size 降到 256效果损失通常小于想象而且迭代更快。4. 从概率到音符采样与解码的工程细节4.1 温度采样与 top-k控制生成的中二程度模型输出的 logits 无法直接变成 Midi必须经过采样策略。这里最常用的手法是温度缩放和 top-k 过滤def sample_next(model, x, temperature0.8, top_k15, devicecuda): model.eval() x x.to(device) with torch.no_grad(): logits, _ model(x) logits logits[0, -1] / max(temperature, 1e-6) probs torch.softmax(logits, dim-1) if top_k is not None: # 只保留概率最高的 k 个事件其余全部置为 0 再重新归一化 values, indices torch.topk(probs, top_k) mask torch.zeros_like(probs) mask.scatter_(-1, indices, 1.0) probs probs * mask probs probs / probs.sum() # 按概率分布采一个事件 id return torch.multinomial(probs, 1).item()温度参数直接决定生成结果的“冒险程度”温度越低采样越倾向于概率最高的事件节奏会稳健但容易单调温度越高越容易跳出固定模式中间值 0.8 到 0.9 最常用。top_k 的作用是砍掉概率排序靠后的尾巴移除那些低概率的怪异事件。我在调参时会把 temperature 和 top_k 分开调稳定性和听感问题优先动 temperature结构混乱和跳变问题优先调 top_k。两者不建议同时设得很极端比如 temperature0.6 且 top_k5生成结果会反复重复同一小段旋律。4.2 事件序列解码回 Midi 文件这里最大的坑在于事件里没有时间步长这个物理量。如果你的事件只有 NOTE_ON、NOTE_OFF、TIME_STEP 三种类型那么解码就是维护一个 current_tick 游标def decode_to_midi(event_tokens, id2event, ticks_per_beat480): import mido track mido.MidiTrack() current_tick 0 pending_notes {} # pitch 对应的 note_off tick 位置 for token in event_tokens: kind, pitch, vel id2event[token] if kind TIME_STEP: # 时间前进一个最小单位不过是先累加再落 current_tick 1 elif kind NOTE_ON: track.append(mido.Message(note_on, notepitch, velocityvel, timecurrent_tick)) current_tick 0 # 这个时间只属于这个事件 elif kind NOTE_OFF: track.append(mido.Message(note_off, notepitch, velocity0, timecurrent_tick)) current_tick 0 midi mido.MidiFile(ticks_per_beatticks_per_beat) midi.tracks.append(track) return midi注意这里 TIME_STEP 每次只前进 1 个 tick这是基于“时间增量被离散成固定步长”的简化假设。如果训练时没有做时间量化而 TIME_STEP 事件可以在一个 token 里代表不同跨度解码逻辑需要改为 time 字段直接映射到 delta tick。还有一个实用技巧解码完成后扫描所有 note_on 事件如果某个 pitch 的 note_off 缺失就在最后一个事件之后补一个 note_off。这是模型生成时概率采样带来的副作用听起来不重但会导致许多 DAW 里出现“按住的幽灵音”。4.3 生成循环与静默崩溃的兜底生成就是不断把采样出的 token 追加到输入尾部再把窗口尾部作为新输入喂回模型。这个循环有两个必须预防的问题两者都是我在实际生成几百份 Midi 后才发现的。第一是无限重复。LSTM 学到的概率会在某些模式下形成循环如果没有打断机制后面 90% 的 token 可能是同一个十个事件的循环体。兜底做法是记录最近 N 个 token 的序列如果新生成的 token 导致这个序列重复出现超过 4 次就把温度上调 0.2 再采一次相当于人为打破循环。第二是静默太长。模型可能会输出一大串 TIME_STEP生成了一个非常长的休止符。上限设置为 32 个连续 TIME_STEP再长就强制采一个 NOTE_ON保证输出有可听内容。这两个兜底都属于工程规则不进入模型但能显著提高生成结果的可用率。5. 避坑与常见问题排查midiGenerator 训练与生成绕不开的 5 个坑5.1 loss 一直下降但生成的音乐全是“忽快忽慢的白噪声”现象交叉熵从 5 降到 3表面训练正常把生成的 Midi 放进播放器里音符密度忽高忽低节奏完全错乱。原因时间分辨率量化粒度不当。如果 delta time 被切得太细序列里 TIME_STEP 占据绝对多数模型学到了多输出 TIME_STEP这条偷懒路径真正决定音符的 token 被淹没。音符时值也因此变得不稳定。解决把 delta time 量化桶数从 64 改成 16 或 8让 TIME_STEP 的总量降下来。另一个更稳的做法是改用 duration token让音符结束后跟一个独立的 DURATION 事件模型不需要通过连续 TIME_STEP 来模拟时值。改完后生成节奏会立刻规整很多。5.2 验证集 loss 比训练集高出一大截现象训练 loss 和验证 loss 之间出现巨大鸿沟甚至验证集 loss 不降反升。原因绝大多数情况下是因为数据预处理没有按文件切分同一个 Midi 文件的切片同时落在训练集和验证集。由于 Midi 文件内部动机重复度极高模型在训练时见过了验证切片里的旋律片段验证集形同虚设。解决回到数据切分那一步按文件 ID 划分 train/val同一个文件下所有切片只能属于一侧。还要检查是否多个文件名指向同一首曲子比如不同命名的重复下载文件。我通常会对文件内容做 MD5 去重这个问题在实际扒数据时比想象中常见。5.3 生成结果里有大量孤立的音符对现象生成出的 Midi 里两个音高相同、间隔极短的音符成对出现听起来像快速的重复敲击不是干净的持续音。原因模型同时学出了两个事件路径来实现同一件事一个是 note_on 然后 note_off另一个是 note_on 换另一个 note_on。有些 midi 文件里本身就有同一音高的快速轮指但更多时候是预处理没有合并相邻的重叠 note。解决在预处理时加一个 merge_overlapping_notes 步骤如果同一 channel 同一 pitch 的 note_on 在上一 note_off 尚未结束时到达就把前一个 note_off 视为无效并合并。这不会让数据集信息变少反而会让事件序列更干净模型输出也更稳定。5.4 生成序列活着活着就变成纯休止符现象生成的前 200 个 token 还有旋律往后越来越稀疏最后连续输出 TIME_STEP音乐变成无限休止。原因这是自回归模型的通病生成一旦进入稀疏区域模型会把“上一时刻是 TIME_STEP”当成继续稀疏的证据陷入静默自循环。温度和 top_k 设得过大也会加剧。解决采样时加静默惩罚连续 N 个 TIME_STEP 后强制清零 TIME_STEP 的概率。另一个工程做法是把 TIME_STEP 从训练数据里限制在序列长度的 60% 以内从源头上让模型不要学到过长休止符的分布。还要检查训练序列里是否保留了大段开头静音这部分对该问题贡献极大截断开头 20 个 token 就能看到明显改善。5.5 生成的 Midi 整体音域漂移到极端高音或低音现象生成结果大量集中在 100 以上的极高音区或者是 30 以下的低音区听感像故障乐器。原因训练数据的音高分布和生成采样概率没有对齐。有些数据集偏好高音旋律有些偏好低音伴奏模型学到的是训练集音高分布但它自己采样时更容易固化在几个高概率音高上放大训练分布的长尾。解决在采样后做音高分布修正统计训练集的音高直方图对生成结果中超出训练音高范围 90% 分位的事件重新采样。也可以直接用训练事件数据的音高分布作为采样 mask让模型回到数据先验。本质上这属于推理时约束加了它不会影响训练但对听感提升非常直接。6. 验证生成质量与进阶方向让模型输出更像真正的音乐6.1 客观指标训练集相似度与生成多样性主观试听始终是最终标准但我每次批量生成后都会先跑一套客观指标筛选再听。最便宜的两个指标是音高分布重叠度和事件类型比例。音高分布重叠度计算生成 Midi 与训练集音高直方图的余弦相似度如果低于 0.7说明模型学到的音域偏离了先验。事件比例则看 NOTE_ON、NOTE_OFF、TIME_STEP 三类事件的占比正常曲子 TIME_STEP 约占 50%~60%如果这个比例超过 80%大概率进入了稀疏静默区。用脚本统计后我会把边缘样本先过滤掉再进入人工试听这能省下大量时间。多样性方面可以看生成集内部的自相似性比如随机抽 20 首生成结果两两对序列做最长公共子序列长度如果过长说明模式固化明显。6.2 进阶控制风格条件与规则后处理模型跑通后最值得投入的进阶方向是条件生成。做法很简单在每条训练序列开头插入一个风格 token比如“风格钢琴曲”“风格游戏配乐”然后让模型学会在这个 token 条件下做预测。生成时在开头放同样的 token输出就会向该风格偏移。这个技巧不需要改模型结构只改事件词表等于用纯数据手段做了可控生成。我更常用的进阶手段其实是规则后处理。拿到模型采样结果后先做一次力度归一化让所有音符力度落在训练集常见区间再修正过短音符时值小于 2 tick 的 note 直接并入前一个音符最后做和弦对齐把同一时刻开始的多个音符统一结束时间。这些步骤全部发生在解码到 Midi 之后不改模型分毫。它们解决的问题是模型输出常见的“力度突兀”“短音杂乱”“和弦不齐”效果立竿见影。做完这套流程出来的 Midi 可以直接进宿主软件叠音色导出基本不需要人工清谱。我自己的习惯是先定好验证指标再动手改模型。否则很容易在“听起来还行”的状态下忽略生成集的统计偏移等到批量上线才发现所有输出都挤在一个八度里。每轮训练后一定保留 checkpoint采样前先固定随机种子方便同一份模型不同采样策略的公平对比。希望这套从数据预处理到采样解码的完整路径能帮你少走几趟弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

rs.open 语句详解:用 TaoToken 统一 Key 调试 SQL 游标与锁定配置 2026/9/29 20:21:59

rs.open 语句详解:用 TaoToken 统一 Key 调试 SQL 游标与锁定配置

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

阅读更多 →
别再问“AI写作哪个第一”了:抢险救援指挥专业的论文工具组合,这样选更靠谱 [特殊字符] 2026/9/29 20:21:59

别再问“AI写作哪个第一”了:抢险救援指挥专业的论文工具组合,这样选更靠谱 [特殊字符]

如果你学的是工学 / 公安技术类 / 抢险救援指挥与技术,大概率会遇到一类很典型的毕业任务:围绕一场典型灾害,做一套“问题分析—指挥流程优化—救援力量调度—风险评估”的研究。 比如论文题目可以具体到: 极端暴雨条件下城市下穿…

阅读更多 →
本科毕业设计提效指南:TaoToken统一Key接入PaperXie与DeepSeek的4种AI科研辅助配置实战 2026/9/29 20:21:59

本科毕业设计提效指南:TaoToken统一Key接入PaperXie与DeepSeek的4种AI科研辅助配置实战

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

阅读更多 →
Unity 材质预览才弹 d3dcompiler_47.dll?先分清编辑器 SDK、着色器缓存和玩家包 2026/9/29 20:21:59

Unity 材质预览才弹 d3dcompiler_47.dll?先分清编辑器 SDK、着色器缓存和玩家包

游戏玩家包能跑,一打开 Unity 材质球或 Unreal 着色器编译窗口就提示找不到 d3dcompiler_47.dll,说明缺的是编辑器编译期用的着色器编译库,不是显卡驱动。这个文件可能来自 Windows SDK,也可能被编辑器自己的 Binaries 带着。先记…

阅读更多 →
OpenClaw Docker手工部署避坑指南:TaoToken统一Key接入与配置验证 2026/9/29 20:21:59

OpenClaw Docker手工部署避坑指南:TaoToken统一Key接入与配置验证

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

阅读更多 →
轮盘抽奖代码实战:用 TaoToken 统一 Key 打通抽奖接口配置 2026/9/29 20:21:47

轮盘抽奖代码实战:用 TaoToken 统一 Key 打通抽奖接口配置

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

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉