声纹识别技术全解:从原理、特征提取到工程部署与避坑指南
做机器听觉这些年声纹识别Voiceprint Recognition是身边人问得最多的方向之一。很多朋友觉得人脸识别早就普及了声音也该能像指纹一样拿来做身份认证。这个想法没有错但落到工程上坑不少。声纹识别要解决的问题很纯粹判断“你是谁”而不是“你说了什么”。它跟语音识别完全不是一个赛道却经常被摆在一起混淆。这篇文章我从实际项目经验出发不堆论文公式把声纹识别的原理、特征提取、模型训练、工程部署和常见坑位一起讲透。适合两类人一是刚接手说话人识别项目的开发者二是准备把声纹能力放进产品的产品和技术负责人。无论你有没有语音基础按这条线走下来至少不会再被网上那些零散的教程带偏。1. 先搞清楚声纹识别到底在解决什么问题1.1 语音识别与声纹识别是两码事我遇到不少需求方一开口就说“我要做一个声纹识别把用户说的话转成文字。”这种需求其实把两个系统合并了。正确表述是一、语音识别ASR把语音转成文字二、声纹识别说话人识别判断说话人身份。常见混淆点语音识别 ASR声纹识别 Speaker Verification核心问题说了什么是谁在说输入语音信号语音信号输出文本转写说话人ID / 相似度分数典型场景字幕、语音输入、会议记录门禁、电话身份验证、支付评价指标字错误率 WER等错误率 EER / 误接受率 FAR / 误拒绝率 FRR为什么要先分清楚因为技术栈完全不同ASR要处理语言模型和词典声纹识别只需要关注“发音者特征”不需要关心语义。工程架构如果混在一起前端特征、模型选型、数据标注都会跟着乱。我见过有人把ASR的wav2vec特征直接拿去训练声纹模型结果同一句话不同人之间的特征差异比不同句子同一人的差异还大效果自然可想而知。1.2 声纹为什么能当“身份ID”声音像指纹吗不完全像但足够唯一用于识别。说话人的声学特征来自两块生理特征声带长度、声道形状、口腔鼻腔共鸣方式决定了声音的“底色”行为特征语速、韵律、停连、咬字习惯决定了说话风格的“纹路”。这两者叠加构成相对稳定的身份标记。但要注意声音不是绝对静止的。感冒、疲劳、年龄变化、情绪波动都会让同一人的语音特征发生偏移麦克风、传输信道、背景噪声也会改动信号。这也是为什么声纹识别不是做“精确匹配”而是做“概率打分 阈值判决”。你可以把声纹理解成人脸识别里的相似度系统输出一个0到1之间的分数分数超过预设阈值才认定是同一个人。这个“阈值”看起来简单却是整个系统里最需要抠的业务参数。门禁场景能容忍用户多试两次但决不允许陌生人混进支付场景则反过来宁肯拒绝一次正常用户也不能放错一笔交易。所以声纹识别从来不是一个模型跑完就结束后面那套评估和决策逻辑才真正决定落地效果。2. 从波形到特征前端处理和特征提取怎么做2.1 语音信号先要“切干净”声纹模型不可能直接吃原始波形主流做法还是先提取声学特征。第一步是采样率统一常见任务用16kHz电话语音只有8kHz两者对声道信息的表达有差异。第二步是分帧加窗语音是非平稳信号但短时间25毫秒左右内相对平稳所以切成帧处理帧移一般取10毫秒加窗函数比如汉明窗是为了减少频谱泄漏。第三步是端点检测VAD去掉静音和长时间噪声防止模型把呼吸声、键盘声当成身份特征。# 伪代码示意音频预处理流程 waveform load_audio(enroll.wav, target_sr16000) # 重采样 frames framing(waveform, frame_len_ms25, hop_ms10) # 分帧 frames apply_window(frames, windowhamming) # 加窗 valid_frames vad(frames) # 端点检测 features extract_features(valid_frames) # 如 MFCC / Filter Bank这个环节最容易被忽略却最决定成败。我见过太多离线测试不错、上线就崩的项目最后都发现VAD切出的语音不是过长就是过短导致模型输入分布和训练时不一致。还有个很容易踩的坑注册阶段用户坐在安静房间里说一句话验证阶段换成嘈杂的地铁里VAD切出来的有效语音长度差异极大分数就跟着剧烈波动。好的做法是不止做一个能量VAD还要组合语音活动检测和信噪比判断尽量把“有效说话段”识别准。2.2 MFCC、i-vector再到深度学习嵌入MFCC梅尔频率倒谱系数是最经典的特征模拟人耳对低频敏感、高频迟钝的非线性听觉特性。它把一段语音变成一组倒谱系数已经包含了声道形状等说话人信息。但MFCC本身不是说话人向量它只是模型输入。传统方法用GMM-UBM先在大规模背景语音上训练一个通用高斯混合模型再用某个人的语音做自适应形成这个人的模型。i-vector在这个基础上更进一步用因子分析把变长语音压成一个固定维度的向量。你可以把i-vector理解成一个“声音压缩包”不管音频有多长最终都变成一条低维向量。深度学习时代常用的是x-vector用网络把帧级特征映射成帧级向量再通过统计池化聚合成整段语音的段级向量。后来ECAPA-TDNN在帧级部分加入了挤压激励模块和更多上下文短语音的鲁棒性更好。现在工程上选型直接拿ECAPA-TDNN当基线不会有大的偏差。方法特征表达维度优点短板GMM-UBM概率模型参数不固定可解释短语音效果差i-vector低维实数向量通常100~600计算轻深度语义不足x-vectorDNN嵌入向量通常512短语音好比ECAPA略弱ECAPA-TDNNDNN嵌入向量192~512更强的短语音 / 噪声鲁棒性训练成本更高这里要强调不管哪种方法最终比对都用余弦相似度或等价形式。模型输出的不是概率而是一个向量你要把它变成分数再变成“通过/不通过”。向量维度和距离度量方式一旦定下来后面调起来就有了稳定抓手。3. 模型训练我踩过的坑和关键参数3.1 数据准备别急着上模型数据决定上限。声纹识别需要大量“同一人多条语音”的数据集。说话人数量至少几百人起步每个说话人最好有20条以上语音覆盖不同日期、时段、通话信道、情绪状态每条2到10秒。更重要的是划分方式训练集、验证集、测试集必须按说话人划分绝不能为了凑样本把同一个人的语音同时塞进训练和测试否则指标会虚高上线后完全不是那么回事。数据增强是必备环节加噪、混响、音量扰动、随机裁剪。增强时小心不要损伤说话人特征比如把声音变速有些人会失去原本的节奏特征。我在一个模拟项目X里拿到2500个说话人的客服录音约60小时有效语音。先做VAD清洗去掉大量重叠人声和背景音乐再切成长度不等的小段。最终按说话人拆成8:1:1验证集和测试集完全没见过训练集里的说话人。清洗阶段最花时间。客服录音有客服和客户两个角色如果用说话人分离工具先做角色分离能明显改善切分质量。但分离后可能存在重采样或压缩需要统一处理。另外标注错误是最大的隐形炸弹。有一次我们模型怎么调EER都降不下来最后排查发现是300多段音频的说话人标签贴反了。清洗数据这种事宁可慢一点也不要赌自动脚本不会出错。3.2 骨干网络与损失函数的选择骨干网络选ECAPA-TDNN输入用80维Filter Bank特征输出说话人embedding常用512维。损失函数用AM-Softmax。普通softmax解决多分类但它的决策面没有给“身份间距”留余量。AM-Softmax在cos角度上加一个margin m和scale s迫使同类特征聚得更紧、异类分得更开。训练细节有几个通用经验使用Adam优化器权重衰减设1e-4左右学习率用warmup cosinewarmup步数500到1000峰值1e-3末端降到1e-5batch size根据GPU显存能多大就多大常用32到64每段音频随机裁剪到2到4秒。固定随机种子避免结果一直在抖动不然你都不知道某个改动是有效还是运气。for epoch in range(epochs): for batch in dataloader: embeddings model(batch.audio) loss am_softmax(embeddings, batch.speaker_id, marginm, scales) optimizer.zero_grad() loss.backward() clip_grad_norm_(max_norm5) optimizer.step() scheduler.step()简单说一下我踩过的坑一开始在训练集上跑得很顺EER却始终不降后来发现数据加载器把每条语音都padding到固定长度而且没用mask机制静音帧全被送进了统计池化等于每句语音都掺了一大段“空白身份”。改成随机裁剪并统一有效长度后效果立刻提升。声纹模型不是越长越准也不是越短越快关键是训练和线上的长度分布要对齐。3.3 相似度阈值和EER到底怎么算开始评估前先理解两个指标。FAR是“不是我的人被放进来”的概率FRR是“我的人被挡在门外”的概率。把阈值抬高FAR降、FRR升阈值压低FRR降、FAR升。EER就是FAR和FRR相等的点用来横向对比模型但真正上线阈值要按业务风险定。具体做法分注册和验证两步。注册阶段每个说话人拿3到5条语音提取embedding后做L2归一化再取平均验证阶段待测语音提取embedding与注册向量算余弦相似度。用一个测试集画出DET曲线再选择阈值。我在模拟项目X里用ECAPA-TDNN在内部中文测试集上得到EER 3.4%。业务方要求误接受不能超过1%最终把阈值设在0.58附近此时FRR约5%。这个组合在门禁场景够用但如果是支付场景还需加活体检测。要特别注意EER低不代表业务可用。EER是平衡点真正上线是在DET曲线上“挑点”不是拿一个默认0.5阈值硬跑。4. 工程落地把声纹识别跑起来4.1 一条完整的识别链路长什么样一个生产可用的声纹识别服务不只是加载模型算一个相似度。完整链路是音频采集麦克风或录音文件统一重采样。端点检测VAD找到有效语音段截掉静音和空白。特征提取分帧加窗得到模型需要的特征。嵌入提取模型把特征转换成说话人embedding。注册/比对与已注册的embedding算得分。决策输出结合阈值返回身份ID、相似度分数和置信度。实时服务通常做成流式麦克风推流进来服务端在缓冲里做VAD检测到一个完整说话段后才触发一次推理。这样做可以避免一句话被切成几段分别打分造成分数不稳定。离线场景则简单很多把历史录音批量过一遍再按业务需求聚合结果。说话人库管理也不难小规模几千人可以直接在内存里存向量每次都全量遍历比对万人以上用向量检索库做近似最近邻召回再在召回集合内做精确余弦打分。注意向量检索的召回率不能影响最终准确率一般要保留Top 20到50再做精排。声纹这个任务对库里“长相相似”的声音特别敏感检索召回如果漏掉了真正的目标后面打分再准也没用。4.2 模型部署和数据格式的坑部署时最常见的问题不是模型跑不快而是前后处理不一致。训练时你做了降噪、分帧、加窗、均值方差归一化上线时如果少了任何一步分数都会变。建议把预处理统一封装成一个服务模块训练和线上的处理逻辑共用同一套代码。有人说这不就是“配置对齐”吗对但就是这么基础的事情项目里最容易被忽略。音频格式方面不同来源的采样率、位深、单双声道都要先标准化。我在项目里踩过最典型的坑客户上传的音频表面上是16kHz实际上是44.1kHz重压的特征分布就全乱了。还有一个坑是单声道与双声道混用有些采集设备左右声道内容不同如果你只取一个声道另一路的信息就丢了如果直接平均又会引入相位抵消。正确做法是稳定使用同一侧声道并写入处理文档。模型导出可以用开放神经交换格式ONNXCPU部署时可考虑INT8量化把单条音频推理压到几十毫秒。但量化前务必在验证集上复查EER不能只看速度。我记得有一次量化后速度提高三倍但误接受率翻倍直接放弃了那个量化策略。4.3 防重放和防伪造不能省声纹识别最大的安全风险不是“认错人”而是“录音重放”。攻击者只要拿一段你正常说话的声音在麦克风前重放一遍很多系统就会放行。这个风险在金融、门禁、远程开户场景里是致命的。基础防御是动态文本验证时随机念一串数字或一句话不能提前录制固定口令。更进一步是活体检测判断音频是否来自真实人发声而不是扬声器播放。典型特征包括背景反射、扬声器失真、低频共振等。也可以做多模态融合同时做人脸识别和声纹识别音画特征交叉校验。做金融类项目时别把防伪当成“后期优化项”。从方案设计一开始就加进去否则上线后被攻击是很正常的。有一次我看到某系统只需要固定朗读一句普通口令三段不同人的录音直接通用这种安全性基本等于没有。就算你模型EER做得再低也架不住攻击者用一段高质量的合成语音反复试。5. 常见问题速查这些坑我替你踩过了5.1 离线测试效果好上线就崩先别急着怀疑模型80%是数据分布漂移。离线集可能都是同一种麦克风、同一种信道、安静环境线上场景五花八门。排查步骤第一步抽一批线上真实数据人工听看有没有串音、混响、截断。第二步对比线上数据频谱和训练集频谱检查信道差异。第三步改进VAD保证送入模型的语音长度和训练时一致。第四步用线上小样本做域自适应比如不改模型只调整特征的均值方差。我的体会是前端和测试集构建比模型本身更值得花时间。模型换掉重训可能只提升1%VAD和信道预处理做对了能提升一大截。上线崩溃的声纹项目里最后真正起作用的基本都是前端修复。5.2 同一个用户两次验证分数波动大常见原因有四个注册语音太短比如只有1秒向量不稳定注册语音和验证语音来自不同渠道比如手机录音和电话说话人本人状态变化比如感冒、酒后、过度紧张麦克风距离变化导致音量起伏和混响差异。对应策略也成熟注册时长至少10秒尽量用多条语音取平均验证语音至少3秒太短就提示用户重说。有条件的话允许在后续交互中一旦验证成功就更新注册模板让模板贴近用户当前状态。这个“模板更新”动作在门禁这类高频场景里很好用但金融场景要谨慎别让攻击者通过低质量尝试一步步把模板拉到自己的方向。5.3 误接受率高怎么办误接受高先分两种情况。第一种测试集里不同人的embedding本身靠得太近说明特征模型或者训练数据不够。第二种是重放攻击攻击者每次都能拿到高分。常规处理办法提高阈值但评估FRR上升是否可接受增加注册样本数量从1条变5条通常能有效压低误接受换更强模型比如从x-vector换到ECAPA-TDNN加防重放模块把扬声器重放直接拒掉。还有一种很容易忽略的情况注册库里的某些用户本身声音很像比如同卵双胞胎这种“天然高相似”无论怎么调阈值都很难区分只能靠多模态或交互验证兜底。5.4 训练不收敛或是同批次跳点训练曲线抖动厉害先查三件事数据标签是不是错了、学习率是不是太大、音频特征是不是没有归一化。逐个排查后基本能解决。经验做法固定随机种子让实验结果可复现。先拿一小批数据让模型过拟合如果过拟合不了说明模型或损失函数有问题。使用warmup cosine学习率稳定性比恒定学习率好得多。梯度裁剪max_norm设在5以内。如果EER在验证集上一直不降也不要急着换模型。先检查测试集里是否混入了同一个人多段语音再检查注册方式。很多所谓模型不行其实是评估脚本写错了。

相关新闻

Nacos 3.x 集成PostgreSQL插件:从单机到集群的实战指南

Nacos 3.x 集成PostgreSQL插件:从单机到集群的实战指南

前阵子帮一个朋友团队收尾 Nacos 集群升级,他们全栈都在用 PostgreSQL,结果 Nacos 2.x 时代只能被 MySQL 绑死,要么放弃统一数据库栈,要么自己改代码打补丁,升级一次崩一次。后来切到 Nacos 3.x 的 PostgreSQL 插件方案…

2026/10/17 7:14:05 阅读更多 →
从Chat到Agent:Solon AI带你构建行动派大模型应用

从Chat到Agent:Solon AI带你构建行动派大模型应用

从 Chat 到 Agent:Solon AI 带你进入“行动派”大模型时代你要注意一个事实:过去两年我们习惯的大模型,基本还停留在“聊天框”里。你问它答,它给你一段漂亮的文字,然后呢?然后你自己去查资料、填表格、发邮…

2026/10/17 7:13:05 阅读更多 →
MATLAB截图原理与ScreenCapture工程实践指南

MATLAB截图原理与ScreenCapture工程实践指南

简介:这是一份专为MATLAB用户设计的轻量级屏幕截图工具包,面向科研人员、工程技术人员及高校师生,解决在MATLAB环境中快速捕获图形窗口、命令行或工作区图像并保存为文件的实际需求,尤其适用于教学演示、实验报告配图与项目过程记…

2026/10/17 7:13:05 阅读更多 →

最新新闻

Vue3 深坑:v-model 对象字段不更新?Proxy+computed 封装 useVModel

Vue3 深坑:v-model 对象字段不更新?Proxy+computed 封装 useVModel

1. 从一个真实翻车现场说起1.1 表面正常&#xff0c;实则断链的 v-model先还原我当时在内部后台项目里遇到的一个诡异问题。父组件维护了一个表单对象&#xff1a;const form ref({user: { name: 张三, age: 28 },tags: [前端, Vue3] })然后把它传给子组件<UserForm v-mode…

2026/10/17 7:50:14 阅读更多 →
artcraft项目实战:从零搭建生成艺术与创意编程的轻量级方案

artcraft项目实战:从零搭建生成艺术与创意编程的轻量级方案

1. 从“artcraft”这个名字说起&#xff1a;它到底想解决什么问题第一次看到“artcraft”这个标题&#xff0c;我脑子里蹦出来的第一反应是“艺术”加“手艺”的组合词。直觉告诉我&#xff0c;这不是一个纯技术框架的名字&#xff0c;也不是某个具体产品的品牌名&#xff0c;它…

2026/10/17 7:50:14 阅读更多 →
基于Python与大数据的高校毕业生招聘信息推荐系统设计与实现

基于Python与大数据的高校毕业生招聘信息推荐系统设计与实现

1. 从"手动刷招聘"到"自动筛岗"&#xff1a;这个项目的出发点又到一年毕业季&#xff0c;我在某高校的信息中心帮忙做就业数据相关的技术支持。一位负责就业指导的A老师跟我聊起一个很现实的问题&#xff1a;他们学校每年毕业生几千人&#xff0c;就业信息…

2026/10/17 7:50:14 阅读更多 →
从基础语法到工程级实践解析Python错误处理的完整指南

从基础语法到工程级实践解析Python错误处理的完整指南

前言 语法层面的 try 与 except&#xff0c;几乎没人不会写&#xff1b;但把它放到一个真实项目里&#xff0c;麻烦立刻来了&#xff1a;这个函数该不该捕获&#xff1f;捕获了之后是处理掉还是往上抛&#xff1f;日志该在哪一层打&#xff1f;为什么线上日志里满屏是「出错了」…

2026/10/17 7:50:14 阅读更多 →
antSword 2.1.9 源码解析:Electron 模块加载与二次开发实战

antSword 2.1.9 源码解析:Electron 模块加载与二次开发实战

简介&#xff1a;中国蚁剑&#xff08;AntSword&#xff09;2.1.9版本完整源码包&#xff0c;面向Web安全测试人员、渗透测试学习者及希望研究安全工具实现原理的开发者。蚁剑是一款轻量级开源Web安全测试平台&#xff0c;支持远程连接管理、插件扩展、跨平台运行&#xff0c;并…

2026/10/17 7:50:14 阅读更多 →
小白程序员必备:收藏这份大模型学习路线图,从入门到实战全解析!

小白程序员必备:收藏这份大模型学习路线图,从入门到实战全解析!

本文提供了一份详尽的大模型学习路线图&#xff0c;分为七大阶段&#xff1a;基础知识打底&#xff08;数学编程&#xff09;、机器学习入门、深度学习、自然语言处理&#xff08;NLP&#xff09;、大模型核心&#xff08;Transformer预训练&#xff09;、大模型应用实战以及持…

2026/10/17 7:49:14 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介&#xff1a;《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南&#xff0c;重点围绕V3与R1两款模型的能力差异与适用场景展开&#xff0c;帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件&#xff0c;压缩包大小约311KB&#xff0c;篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介&#xff1a;这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档&#xff0c;共收录两百余条可直接套用的润色指令&#xff0c;涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度&#xff0c;并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介&#xff1a;本资源是一份基于OpenCV的本科课程设计完整报告&#xff0c;面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生&#xff0c;解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →