Python字幕翻译脚本实战:SRT解析、翻译接口调用与时间轴对齐
1. 字幕翻译这件事为什么值得用Python折腾一遍做字幕翻译这个需求最早是从一个很具体的场景来的手里攒了一批外语电影想看的片子要么压根没有中文字幕要么机翻字幕质量惨不忍睹时间轴还对不上。手动翻译一部两小时的电影光是逐句敲字就得搭进去一整天更别提还要反复校对时间轴。这种重复劳动交给程序去做才是正经事。Python在这件事上有天然优势。字幕文件本质上就是带时间戳的纯文本格式规整、结构清晰解析起来毫无难度。再加上现在翻译接口的生态足够成熟把读取字幕→提取文本→调用翻译→回写时间轴这条链路串起来一个脚本就能搞定。整个过程涉及的核心技术点包括字幕格式解析SRT/ASS、批量文本处理、翻译接口调用与限流、时间轴对齐、编码处理。听起来像是个小工具但真动手做坑一点都不少。这篇文章适合谁看如果你有一点Python基础能看懂函数和循环想给自己做一个趁手的字幕翻译工具那接下来的内容可以直接抄作业。如果你完全没写过代码也没关系我会把每一步的逻辑讲清楚你照着改参数也能跑起来。整篇内容围绕一个目标让你拥有一套能实际用起来的字幕自动翻译方案而不是停留在知道有这么回事的层面。2. 整体方案设计与技术选型思路2.1 为什么选Python而不是现成工具市面上确实有一些现成的字幕翻译软件但用下来总有几个绕不过去的问题。要么是翻译质量不可控要么是批量处理要付费要么是格式支持不全。自己写脚本的好处在于每一个环节你都能控制用哪个翻译源、怎么处理断句、时间轴怎么对齐、遇到错误怎么重试全部由你说了算。Python的生态在这里体现得很明显。pysrt库专门处理SRT字幕chardet搞定编码检测翻译环节可以用各种翻译API整个流程用标准库的argparse做个命令行入口几十行代码就能跑通核心逻辑。更重要的是脚本可以随时改今天想换个翻译源改几行就行明天想加个批量处理功能加个循环就完事。这种灵活性是现成工具给不了的。2.2 核心流程拆解整个工具的逻辑链条其实很直白我把它拆成五个环节读取字幕文件识别编码解析出每条字幕的序号、时间轴和文本内容文本预处理清理特殊符号、合并过短的句子、标记不需要翻译的内容调用翻译接口把文本分批发送处理返回结果做好错误重试回写时间轴把翻译后的文本按原时间轴重新组装输出字幕文件按原格式保存确保播放器能正常识别这五步里最容易出问题的是第二步和第三步。预处理做不好翻译出来的句子会支离破碎接口调用不做限流和重试跑一半就断了。后面会详细说这两块怎么处理。2.3 翻译源的选择逻辑翻译源的选择直接决定了最终效果。我试过几种方案各有优劣方案类型优点缺点适用场景在线翻译API质量高、语种全有调用限制、可能收费对质量要求高的场景本地翻译模型无网络依赖、隐私好速度慢、质量参差离线环境、敏感内容开源翻译库免费、可定制需要自己部署、维护成本高有技术能力且追求免费我的建议是先用在线API跑通流程确认效果后再考虑是否切换到本地方案。对于大多数个人使用场景在线API的免费额度基本够用。如果片子特别多可以考虑多个翻译源轮换使用避免单一接口被限流。注意选择翻译源时要考虑目标语言的翻译质量。不同翻译源在不同语种上的表现差异很大建议先用一小段字幕做测试对比几个源的效果再决定。3. 字幕文件解析的核心细节3.1 SRT格式的结构与解析要点SRT是最常见的字幕格式结构非常简单1 00:00:01,000 -- 00:00:04,000 这是第一句字幕 2 00:00:05,500 -- 00:00:08,200 这是第二句字幕每条字幕由三部分组成序号、时间轴、文本内容中间用空行分隔。看起来简单但实际解析时会遇到几个坑。第一个坑是编码问题。字幕文件的编码五花八门有UTF-8、GBK、BIG5甚至还有带BOM的UTF-8。如果不做编码检测直接读取轻则乱码重则报错。我的做法是用chardet先检测编码再用检测到的编码读取最后统一转成UTF-8处理。第二个坑是时间轴格式的变体。标准SRT用的是逗号分隔毫秒但有些文件用点号有些文件的时间轴前后有空格。解析时要用正则表达式做兼容处理不能硬编码。第三个坑是文本内容里的特殊字符。有些字幕会包含HTML标签比如i斜体标记、位置标记{\an8}或者音乐符号。这些内容如果直接送去翻译会污染翻译结果。需要在预处理阶段识别并处理。3.2 用pysrt解析字幕的实操方法pysrt是处理SRT字幕最顺手的库安装很简单pip install pysrt读取和遍历字幕的基本操作import pysrt subs pysrt.open(movie.srt, encodingutf-8) for sub in subs: print(f序号: {sub.index}) print(f时间轴: {sub.start} -- {sub.end}) print(f文本: {sub.text}) print(---)pysrt会自动处理时间轴的解析和格式化省去了手写正则的麻烦。但有一点要注意pysrt.open()的encoding参数如果不指定它会尝试自动检测但检测结果不一定准确。我的习惯是先用chardet检测一遍再把结果传给pysrt。保存翻译后的字幕同样简单subs.save(movie_translated.srt, encodingutf-8)这里建议统一保存为UTF-8编码兼容性最好。如果目标播放器对编码有特殊要求再单独处理。3.3 ASS格式的兼容处理ASS格式比SRT复杂得多它包含样式定义、特效指令等内容。如果只是翻译对白可以只提取[Events]段落中的Dialogue行进行处理。但ASS的解析用pysrt就不行了需要自己写解析逻辑或者用ass库。我的建议是如果手头的字幕主要是SRT格式就专注做好SRT的处理。遇到ASS格式先用工具转成SRT再处理这样能省去很多兼容性的麻烦。转换工具可以用ffmpegffmpeg -i input.ass output.srt转换后再用统一流程处理逻辑会清爽很多。4. 文本预处理与翻译接口调用4.1 文本清洗的五个关键步骤预处理做得好不好直接决定了翻译质量。我总结了五个必须做的步骤第一步去除HTML标签和特效标记。字幕里常见的i、b、{\an8}这些标记翻译前要剥离翻译后再根据需要决定是否加回。简单的做法是用正则直接去掉import re def clean_text(text): text re.sub(r[^], , text) text re.sub(r\{[^}]\}, , text) return text.strip()第二步处理换行符。字幕里的换行通常是为了显示效果翻译时应该合并成一行翻译完再根据长度决定是否重新断行。第三步标记不需要翻译的内容。比如纯数字、纯符号、音乐符号♪等这些内容翻译了反而奇怪应该原样保留。第四步合并过短的句子。有些字幕把一句话拆成好几条单独翻译会丢失上下文。可以根据时间轴间隔和文本长度把相邻的短句合并后再翻译。第五步统一标点符号。不同语言混用标点的情况很常见统一成目标语言的标点规范能让翻译结果更自然。4.2 翻译接口的调用策略翻译接口的调用有几个关键点批量发送、限流控制、错误重试。批量发送是为了减少请求次数。大多数翻译接口都支持一次发送多段文本把字幕按每批20-50条分组发送效率会高很多。但要注意每批的总字符数不能超过接口限制。限流控制是为了避免触发接口的频率限制。我的做法是在每次请求之间加一个固定间隔比如0.5秒。如果接口返回频率超限的错误就指数退避重试import time def translate_with_retry(texts, max_retries3): for attempt in range(max_retries): try: result call_translate_api(texts) return result except RateLimitError: wait_time 2 ** attempt time.sleep(wait_time) raise Exception(翻译失败已达最大重试次数)错误重试要区分错误类型。网络超时、频率超限这类临时错误可以重试参数错误、认证失败这类永久错误重试也没用应该直接报错。4.3 翻译结果的缓存机制跑长片字幕的时候最怕的就是跑到一半断了重新跑又要从头开始。加一个缓存机制能省很多事。我的做法是把每条字幕的原文和译文存到一个JSON文件里每次翻译前先查缓存有就直接用没有才调接口。import json import os def load_cache(cache_file): if os.path.exists(cache_file): with open(cache_file, r, encodingutf-8) as f: return json.load(f) return {} def save_cache(cache, cache_file): with open(cache_file, w, encodingutf-8) as f: json.dump(cache, f, ensure_asciiFalse, indent2)这个缓存文件还有个额外好处如果对某些翻译结果不满意可以直接改缓存文件里的内容不用重新跑整个流程。提示缓存文件的key建议用原文的哈希值而不是序号。这样即使字幕文件有微调已翻译的内容也不会丢失。5. 时间轴对齐与字幕回写5.1 时间轴对齐的核心逻辑时间轴对齐听起来复杂其实逻辑很简单翻译后的文本长度会变但时间轴不变。也就是说原来在什么时间点出现的字幕翻译后还在那个时间点出现。需要处理的只是显示时长的问题。如果翻译后的文本比原文长很多可能导致字幕显示时间不够观众来不及看完。这时候有两种处理方式一是缩短翻译文本二是适当延长显示时间。我的做法是设置一个阈值如果翻译后文本长度超过原文的1.5倍就检查前后字幕的间隔看能不能借用一点时间。def adjust_timing(subs, max_ratio1.5): for i, sub in enumerate(subs): original_len len(sub.text) translated_len len(sub.translated_text) if translated_len original_len * max_ratio: # 检查下一条字幕的开始时间 if i len(subs) - 1: gap subs[i1].start - sub.end if gap.seconds 1: sub.end sub.end datetime.timedelta(seconds1) return subs这个逻辑不是必须的但对于翻译后文本膨胀严重的情况能明显改善观看体验。5.2 字幕回写的格式保持回写字幕时要尽量保持原文件的格式。pysrt的save()方法会自动处理格式但有几个细节要注意序号连续性如果预处理时合并了字幕序号会变。保存前要重新编号确保从1开始连续递增。空行分隔SRT格式要求每条字幕之间有空行pysrt会自动处理不用手动加。编码一致性保存时的编码要和读取时一致或者统一用UTF-8。如果原文件是GBK播放器可能默认按GBK读取保存成UTF-8就会乱码。这种情况要么保持原编码要么在文件名里标注编码。5.3 双语字幕的输出方案很多人喜欢看双语字幕上面是原文下面是译文。实现起来很简单把原文和译文用换行符拼在一起就行for sub in subs: sub.text f{sub.original_text}\n{sub.translated_text}但要注意双语字幕会让每条字幕的高度增加可能遮挡画面。如果播放器支持调整字幕位置可以在字幕文件里加位置标记。SRT格式本身不支持位置标记但可以通过在文本前加{\an8}这样的标记来实现部分播放器支持。6. 常见问题排查与避坑经验6.1 编码问题速查表编码问题是字幕处理中最常见的坑我整理了一个速查表现象可能原因解决方法读取时报UnicodeDecodeError文件编码与指定编码不符用chardet检测实际编码翻译后字幕显示乱码保存编码与播放器预期不符保持原编码或统一UTF-8部分字符显示为问号编码不支持该字符转成UTF-8再处理文件头有不可见字符UTF-8 BOM读取时用utf-8-sig编码6.2 翻译接口常见错误处理调用翻译接口时常见的错误和应对方法频率超限降低请求频率加长间隔时间或者切换翻译源单次请求过长减小批量大小把长文本拆成多段网络超时增加超时时间加重试机制返回结果为空检查源文本是否为空或全是特殊字符翻译质量差尝试不同的翻译源或者调整预处理逻辑6.3 实操心得与避坑技巧心得一先小批量测试再全量跑。拿到一个新字幕文件先翻译前10条看看效果确认没问题再跑全量。这样能及早发现编码、格式、翻译质量等问题避免跑了一半才发现不对。心得二保留中间结果。翻译过程中把原文、译文、时间轴都存下来万一最后一步出问题不用从头再来。我习惯把中间结果存成JSON方便排查问题。心得三时间轴不要轻易动。除非翻译后文本膨胀特别严重否则不要调整时间轴。手动调时间轴很容易出错而且不同播放器对时间轴的处理方式不一样调了反而可能出问题。心得四注意字幕里的文化特定内容。有些台词包含俚语、典故、双关语机器翻译很难处理好。这种情况可以在缓存文件里手动修正或者在预处理阶段加注释。心得五批量处理时加日志。跑批量任务时把每条字幕的处理状态记到日志里。出问题时能快速定位是哪一条出的错不用大海捞针。7. 完整脚本的组装与运行7.1 脚本结构设计把前面各个环节串起来一个完整的脚本大概长这样import pysrt import chardet import json import time import re import argparse def detect_encoding(file_path): with open(file_path, rb) as f: raw f.read() return chardet.detect(raw)[encoding] def clean_text(text): text re.sub(r[^], , text) text re.sub(r\{[^}]\}, , text) return text.strip() def translate_batch(texts, cache): # 查缓存 results [] to_translate [] for text in texts: if text in cache: results.append(cache[text]) else: results.append(None) to_translate.append(text) # 调用翻译接口 if to_translate: translated call_translate_api(to_translate) for i, text in enumerate(to_translate): cache[text] translated[i] # 组装结果 final [] idx 0 for r in results: if r is not None: final.append(r) else: final.append(cache[to_translate[idx]]) idx 1 return final def main(): parser argparse.ArgumentParser() parser.add_argument(input, help输入字幕文件) parser.add_argument(output, help输出字幕文件) parser.add_argument(--source, defaultauto, help源语言) parser.add_argument(--target, defaultzh, help目标语言) args parser.parse_args() # 读取字幕 encoding detect_encoding(args.input) subs pysrt.open(args.input, encodingencoding) # 预处理 texts [clean_text(sub.text) for sub in subs] # 翻译 cache load_cache(translation_cache.json) translated translate_batch(texts, cache) save_cache(cache, translation_cache.json) # 回写 for sub, trans in zip(subs, translated): sub.text trans subs.save(args.output, encodingutf-8) print(f翻译完成输出文件{args.output}) if __name__ __main__: main()这个脚本把核心逻辑都包含了实际使用时只需要把call_translate_api替换成你用的翻译接口的调用代码。7.2 运行参数与配置说明脚本的命令行参数设计参数说明默认值input输入字幕文件路径必填output输出字幕文件路径必填--source源语言代码auto--target目标语言代码zh--batch-size每批翻译条数20--interval请求间隔秒0.5运行示例python translate_srt.py movie.srt movie_zh.srt --source en --target zh7.3 性能优化建议如果字幕文件特别大或者要批量处理很多文件可以考虑几个优化方向并发处理用concurrent.futures把翻译请求并发化但要注意控制并发数避免触发接口限流增量翻译利用缓存机制只翻译新增或修改的内容预处理优化把清洗、合并等操作提前做好减少翻译时的处理量断点续传把处理进度存到文件里中断后能从上次的位置继续8. 后续扩展方向这套脚本跑通之后能扩展的方向其实挺多的。比如加一个GUI界面让不熟悉命令行的朋友也能用或者接入多个翻译源自动对比选择质量最好的结果再或者加一个术语表功能把特定名词的翻译固定下来保证全片翻译的一致性。我自己最常用的扩展是加了一个翻译质量检查环节把翻译后的字幕再反向翻译回原文对比相似度相似度太低的地方标记出来人工复核。这个功能对于翻译质量要求高的场景特别有用能快速定位到有问题的句子。另外如果经常处理同一语种的字幕可以针对性地优化预处理规则。比如日文字幕的敬语处理、韩文字幕的助词处理都有一些特殊的技巧做多了自然就有经验了。

相关新闻

冒险岛055一树端源码搭建教程:Java服务端环境配置与开服实战

冒险岛055一树端源码搭建教程:Java服务端环境配置与开服实战

简介:这份冒险岛055版本一树端源码,是一套修复程度接近98%的单机/私服服务端完整源码,主要面向怀旧游戏爱好者、游戏开发初学者以及希望研究早期端游服务端架构的技术人员。压缩包采用zip格式,整体大小约10.14MB,平台上…

2026/10/9 14:41:09 阅读更多 →
gstack:YC 掌门 Garry Tan 的 Claude Code 全家桶!23 个 AI 专家当你的 CEO/架构师/QA,TaoToken 统一 Key 接入

gstack:YC 掌门 Garry Tan 的 Claude Code 全家桶!23 个 AI 专家当你的 CEO/架构师/QA,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 14:41:09 阅读更多 →
SpringBoot新生入学系统毕设全解析:从需求到部署

SpringBoot新生入学系统毕设全解析:从需求到部署

每到毕业季,总有人问我:“老师/学长,毕设到底做什么题好?”如果让我给一个既好写、又有实际价值、还能在答辩时讲出亮点的题目,springboot育新中学新生入学系统绝对排得上号。别一看“新生入学系统”就觉得是普通的增删…

2026/10/9 14:40:08 阅读更多 →

最新新闻

全国县级shp文件处理全攻略:从坐标系体检到重投影避坑指南

全国县级shp文件处理全攻略:从坐标系体检到重投影避坑指南

简介:全国县级行政区划shp文件是GIS领域常用的基础地理数据,面向地理信息专业学生、规划人员及数据分析师,用于获取中国县级行政单元的精确边界。Shapefile基于Esri标准格式,由几何、属性、索引三类子文件协同工作,本资…

2026/10/9 15:20:57 阅读更多 →
ADO封装库实战:从Connection到Recordset的核心用法与避坑指南

ADO封装库实战:从Connection到Recordset的核心用法与避坑指南

简介:面向商业开发场景的 ADO 源代码示例包 Ado_Aok_demo.zip,聚焦微软 ActiveX Data Objects 在 C 工程中的实际应用,适合需要在财务、库存、客户关系管理等业务系统中完成数据库连接的开发者参考学习。压缩包共收录 26 个文件,以…

2026/10/9 15:20:56 阅读更多 →
人工智能课后习题答案PDF高效整理:OCR识别、题号映射与复习实战

人工智能课后习题答案PDF高效整理:OCR识别、题号映射与复习实战

简介:这是清华大学出版社《人工智能》教材配套课后习题答案,覆盖搜索算法、知识表示、机器学习、描述逻辑、概率图模型等核心章节,适合高校相关专业学生及自学者用于课后自测与考前梳理。资源包为单个PDF文件,大小约212KB&#xf…

2026/10/9 15:20:56 阅读更多 →
单纯形法实战指南:从线性规划原理到Python代码实现

单纯形法实战指南:从线性规划原理到Python代码实现

1. 从"资源分配"这个老问题说起线性规划这个词听起来像是课本里的东西,但如果你做过任何跟资源分配沾边的事情,你其实已经在跟它打交道了。工厂排产、物流调度、投资组合配比、甚至食堂配菜控制成本,本质上都是同一类问题&#xff…

2026/10/9 15:20:56 阅读更多 →
具身智能大模型如何落地家居服务机器人:架构设计与避坑指南

具身智能大模型如何落地家居服务机器人:架构设计与避坑指南

简介:这份文档面向家居服务机器人与具身智能方向的研究者、工程师及高校学生,系统梳理了具身智能大模型从理论到落地的完整设计路径,帮助读者理解如何让机器人通过感知、决策与行动一体化地服务家庭场景。资源包内含1个docx文档,约…

2026/10/9 15:20:56 阅读更多 →
SQL Server函数大全精析:从能跑到跑对,避开日期、字符串与窗口函数常见坑

SQL Server函数大全精析:从能跑到跑对,避开日期、字符串与窗口函数常见坑

简介:这份SQL Server函数大全精析文档面向数据库开发、运维人员及SQL初学者,系统梳理T-SQL函数的分类与使用要点,帮助读者解决数据计算、类型转换、聚合统计与日期处理等常见问题。资源包内含1个doc文档,约859KB,内容涵…

2026/10/9 15:19:55 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

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/8 15:26:32 阅读更多 →
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/8 15:26:40 阅读更多 →
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/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 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/8 21:13:17 阅读更多 →
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/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练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/9 6:17:20 阅读更多 →