Lightpanda无头浏览器:AI自动化测试的轻量级CDP兼容方案
1. 为什么无头浏览器成了AI自动化的瓶颈做AI自动化的人都有一个共同的体感模型越来越聪明但真正让整个流程跑起来的往往不是模型本身而是那个替AI看网页、点按钮的浏览器。无论是让AI自动填表、抓取结构化数据、跑端到端测试还是做自动化巡检脚本背后都站着一个无头浏览器。问题在于传统方案太重了。我最早做AI自动化测试的时候用的是大家最熟悉的那套组合Chromium加Puppeteer或者Playwright。功能确实全但代价也明显。一个容器镜像动辄几百兆到上G冷启动要等好几秒内存占用轻松上G。当你要同时跑几十上百个并发任务时机器成本直接起飞。更难受的是AI自动化场景里很多任务其实很简单——打开页面、等元素出现、点一下、读个文本——但为了这点事你得背着一整个浏览器内核跑。Lightpanda就是冲着这个痛点来的。它是一个用Zig语言从零写的无头浏览器专门为AI自动化和机器驱动场景设计不追求给人看的渲染效果只追求快、轻、省。它实现了Chrome DevTools ProtocolCDP也就是说你原来用Puppeteer、Playwright写的脚本很多情况下改个连接地址就能直接跑。这篇文章我就把Lightpanda这套东西从设计思路、核心原理、实操部署到踩坑经验完整拆一遍适合正在做AI自动化测试、爬虫、Agent工具链的同行参考也适合想给现有自动化流程降本提速的团队。先说结论性的判断Lightpanda不是要取代完整浏览器它是在AI驱动网页这个特定场景里做减法。理解它减掉了什么、保留了什么你才知道它适不适合你的活。2. Lightpanda的整体设计与技术选型拆解2.1 为什么用Zig重写一个浏览器要理解Lightpanda先得理解它为什么不用现成的Chromium改而是选择Zig从零实现。这个决定背后有几层考量。第一层是体积和启动速度。Chromium是个为人类浏览体验服务的庞然大物它包含完整的渲染引擎、GPU加速、多媒体解码、扩展系统等等。而AI自动化场景根本不需要把页面画出来只需要解析DOM、执行JavaScript、响应CDP指令。把这些用不上的部分砍掉二进制体积能压到几十兆级别冷启动从秒级降到毫秒级。第二层是内存模型的可控性。Zig没有垃圾回收内存分配是显式管理的。对于要跑成百上千并发实例的自动化平台来说可预测的内存占用比平均占用低但偶尔抖动更重要。你可以精确控制每个实例吃多少内存这在容器化部署里非常关键。第三层是依赖的干净。Chromium的构建依赖链极其复杂交叉编译和裁剪都很痛苦。Zig自带交叉编译能力一个命令就能产出目标平台的可执行文件部署时基本是扔一个二进制进去就能跑。注意用Zig重写不代表它兼容所有网页。它目前对复杂CSS布局、WebGL、音视频这些支持有限这是刻意的取舍不是缺陷。选型前一定要确认你的目标页面属于结构脚本型而不是重渲染型。2.2 CDP兼容意味着什么Lightpanda最聪明的一步是实现了CDP。CDP是Chrome DevTools Protocol的缩写是Chrome和外部工具通信的标准协议。Puppeteer、Playwright这些主流自动化库底层都是通过CDP跟浏览器对话的。这意味着迁移成本极低。你原来写的const browser await puppeteer.connect({ browserWSEndpoint: ws://127.0.0.1:9222 });只要把连接地址指向Lightpanda暴露的CDP端口脚本逻辑基本不用动。对于已经在用Playwright或Puppeteer的团队这是一条几乎零改造的升级路径。但这里有个细节要讲清楚CDP是个很大的协议包含几百个域Domain比如Page、Network、Runtime、DOM、Input等等。Lightpanda实现的是其中最常用的子集覆盖页面导航、DOM查询、JS执行、网络请求拦截这些核心能力。一些冷门域比如复杂的性能追踪、部分调试器功能可能没有完整实现。所以兼容CDP不等于100%兼容所有CDP命令这点在评估时要心里有数。2.3 它到底省在哪一张对比表我把Lightpanda和传统方案在几个关键维度上做了对比数据是基于常见部署场景的经验值具体会随版本和页面复杂度浮动维度Chromium系无头浏览器Lightpanda镜像/二进制体积数百MB到1GB几十MB级别冷启动时间秒级毫秒到百毫秒级单实例内存占用数百MB到上GB几十MB级别并发密度单机几十实例单机可上百实例渲染能力完整有限结构脚本为主CDP兼容完整核心子集适用场景通用AI自动化、抓取、测试这张表的核心信息是Lightpanda用渲染能力换密度和速度。如果你的任务需要看页面长什么样、需要截图、需要处理复杂前端框架的视觉交互那它可能不合适。但如果你的任务是让AI理解页面结构并操作它那它几乎是量身定做的。3. 核心细节解析与实操要点3.1 安装与启动从零到跑起来Lightpanda的部署方式很灵活最省事的是直接用官方提供的二进制或者容器镜像。我一般推荐容器方式因为环境隔离干净迁移方便。用容器跑的话基本流程是这样docker run -d \ --name lightpanda \ -p 9222:9222 \ lightpanda/browser:nightly \ serve --host 0.0.0.0 --port 9222启动后它会监听9222端口暴露CDP服务。这里有几个参数值得说--host 0.0.0.0让容器外部能访问如果只在容器内用可以绑127.0.0.1更安全。--port 9222CDP默认端口跟Chrome保持一致方便工具直接对接。serve子命令表示以服务模式运行等待外部连接。如果你不想用容器也可以直接下载对应平台的二进制。Zig编译出来的产物是静态链接的基本不依赖系统库扔到服务器上给个执行权限就能跑chmod x lightpanda ./lightpanda serve --host 127.0.0.1 --port 9222提示生产环境建议绑定内网地址并在前面加一层访问控制。CDP本身没有认证机制谁连上谁就能控制浏览器暴露到公网是危险的。3.2 用Puppeteer连接Lightpanda启动服务后连接方式和连Chrome几乎一样。下面是一段我常用的最小验证脚本const puppeteer require(puppeteer-core); (async () { const browser await puppeteer.connect({ browserWSEndpoint: ws://127.0.0.1:9222, defaultViewport: null, }); const page await browser.newPage(); await page.goto(https://example.com, { waitUntil: domcontentloaded }); const title await page.title(); const text await page.evaluate(() document.body.innerText); console.log(标题:, title); console.log(正文前200字:, text.slice(0, 200)); await page.close(); await browser.disconnect(); })();注意这里用的是puppeteer-core而不是puppeteer。因为puppeteer会尝试下载并捆绑一个Chromium而我们根本不需要它用puppeteer-core只保留客户端逻辑体积小、启动快。waitUntil我一般设成domcontentloaded而不是networkidle。原因是在AI自动化场景里很多页面有长轮询、埋点请求永远等不到网络空闲用networkidle容易超时。等DOM就绪后再针对具体元素做显式等待这样更稳。3.3 元素等待的正确姿势新手最容易踩的坑就是页面还没加载完就去点元素然后报一堆找不到节点的错。正确做法是显式等待目标元素出现await page.waitForSelector(#submit-btn, { timeout: 10000 }); await page.click(#submit-btn);如果页面是动态渲染的元素可能延迟出现waitForSelector会自动轮询直到超时。超时时间别设太短网络抖动时容易误判也别太长否则失败任务会拖住整个队列。我一般给10到15秒具体看目标站点的响应特征。还有一个进阶技巧用waitForFunction等待某个条件成立比如等待某个全局变量被赋值、等待列表长度大于0await page.waitForFunction( () document.querySelectorAll(.item).length 0, { timeout: 15000 } );这比死等固定时间靠谱得多也比单纯等选择器更灵活。3.4 网络请求拦截与资源裁剪AI自动化里经常需要拦截请求比如屏蔽图片、字体、广告脚本只保留HTML和关键JS进一步提速。CDP支持请求拦截Puppeteer里可以这样写await page.setRequestInterception(true); page.on(request, (req) { const type req.resourceType(); if ([image, font, media].includes(type)) { req.abort(); } else { req.continue(); } });在Lightpanda上这套逻辑同样适用因为它实现了Network域的核心能力。实测下来屏蔽掉图片和字体后页面加载时间能明显下降尤其对图片密集的站点效果显著。注意拦截规则要谨慎。有些页面的关键逻辑依赖某个JS文件如果你按类型一刀切把script也拦了页面就废了。建议先只拦image、font、media这三类确认没问题再考虑更激进的裁剪。4. 实操过程与核心环节实现4.1 搭建一个AI自动化测试的最小闭环光跑通连接不算本事真正有价值的是把它接进一条完整的AI自动化链路。我以一个典型的AI驱动表单填写与提交任务为例把整个闭环拆给你看。第一步准备一个任务描述。AI自动化里任务通常由模型生成或从配置读取比如打开注册页填入用户名和邮箱点击提交验证成功提示。第二步把任务翻译成浏览器操作序列。这一步可以用规则引擎也可以让模型输出结构化指令。我倾向于让模型输出JSON格式的动作列表再由执行器逐条执行这样可控性更强[ { action: goto, url: https://example.com/register }, { action: type, selector: #username, value: testuser }, { action: type, selector: #email, value: testexample.com }, { action: click, selector: #submit-btn }, { action: waitForSelector, selector: .success-tip, timeout: 10000 } ]第三步执行器把动作映射到Puppeteer调用。这里的关键是每个动作都要有超时和错误处理不能让某一步卡死拖垮整个任务。第四步采集结果。成功与否、页面文本、截图如果支持、耗时都记录下来回传给上层做判断或喂给模型做下一步决策。这套闭环跑起来后你会发现瓶颈往往不在模型而在浏览器的启动和并发能力。这正是Lightpanda的用武之地——同样的机器你能跑的并发任务数量可能翻好几倍。4.2 并发与资源控制AI自动化平台通常要同时处理大量任务并发控制是核心。用Lightpanda时我建议按进程级隔离来设计每个任务或每组任务起一个独立的Lightpanda实例通过不同端口暴露CDP任务结束后销毁实例。这样做的好处是故障隔离。一个实例崩了不影响其他任务也不会因为某个页面的内存泄漏拖垮整个服务。代价是实例管理变复杂需要一套调度逻辑来分配端口、监控存活、回收资源。一个简单的端口分配思路是维护一个端口池任务开始时从池里取一个空闲端口启动实例任务结束后关闭实例并把端口还回池子。配合容器编排的话可以直接用Kubernetes的Pod来承载每个实例用Service暴露CDP端口扩缩容交给编排系统。资源限制方面给每个实例设内存上限很重要。Zig的内存可控性在这里体现价值——你可以比较准确地预估单实例占用从而算出单机能跑多少并发。经验值是单实例几十MB级别具体取决于页面复杂度重JS页面会更高。4.3 与AI Agent工具链的集成现在很多AI Agent框架比如各种浏览器工具、自动化Agent底层都依赖CDP。Lightpanda接进去的方式本质上就是替换CDP端点。以常见的Agent工具为例配置里通常有一个browserWSEndpoint或者cdpUrl字段把它指向Lightpanda的地址即可。如果工具内部硬编码了启动Chromium的逻辑那就需要改造成连接已有浏览器模式。这里有个实操心得集成前先用一个最简单的脚本验证CDP连通性和核心命令是否可用。因为不同Agent工具用到的CDP命令集不一样有的会调用一些Lightpanda尚未实现的域跑起来才发现不兼容就浪费时间了。验证清单我一般包括导航、DOM查询、点击、输入、执行JS、等待元素这几项过了基本就能用。4.4 一个完整的部署配置示例把上面这些串起来我给一个相对完整的部署配置用容器编排的方式跑多个Lightpanda实例services: lightpanda: image: lightpanda/browser:nightly command: serve --host 0.0.0.0 --port 9222 ports: - 9222:9222 deploy: replicas: 4 mem_limit: 256m restart: unless-stopped这里replicas: 4表示起4个实例mem_limit: 256m给每个实例设内存上限。实际生产里你会用更专业的编排系统但思路是一样的多实例、限内存、自动重启。连接时按实例地址分发任务比如用轮询或一致性哈希把任务分到不同实例避免单个实例过载。5. 常见问题与排查技巧实录5.1 连接失败与端口问题最常见的第一个坑是连不上。排查顺序我一般是这样先确认服务真的起来了docker ps看容器状态或者直接curl一下CDP的HTTP端点curl http://127.0.0.1:9222/json/version如果返回一段JSON说明服务正常如果连接被拒那就是服务没起或者端口没映射对。容器场景下特别容易犯的错是端口映射写反了或者--host绑成了127.0.0.1导致容器外访问不到。还有一个隐蔽问题CDP的WebSocket地址。/json/version返回的webSocketDebuggerUrl里可能带的是容器内部地址外部连的时候要替换成实际可达的地址。Puppeteer的browserWSEndpoint要填对否则会一直重连失败。5.2 页面加载超时第二个高频问题是超时。表现是page.goto一直不返回最后抛超时错误。原因通常有几类一是目标页面本身慢或者有大量阻塞资源。解决办法是设合理的timeout并配合请求拦截裁剪资源。二是waitUntil设得太严格。前面说过networkidle在长连接页面基本等不到换成domcontentloaded或load。三是页面有反自动化检测故意拖慢或返回空内容。这种情况要检查请求头、User-Agent必要时模拟更真实的浏览器指纹。不过要注意绕过检测涉及合规问题务必在合法授权范围内操作。5.3 元素找不到元素找不到的原因很多我整理成一张速查表现象可能原因排查方向选择器报错选择器语法错误在浏览器控制台验证选择器元素存在但找不到在iframe内先切换到对应frame动态渲染延迟元素还没生成用waitForSelector显式等待页面结构不同多版本页面打印DOM确认实际结构被遮挡元素不可交互滚动到可视区或等待动画结束其中iframe是最容易被忽略的。很多登录、支付组件嵌在iframe里直接在主文档找选择器永远找不到。要先page.frames()列出所有frame找到目标frame再操作。5.4 内存增长与实例回收跑久了发现内存慢慢涨这是无头浏览器的通病。Lightpanda虽然内存可控但长时间运行同一个实例处理大量页面还是会有累积。我的做法是给实例设任务数上限比如处理完100个任务就主动销毁重建。这样虽然牺牲一点启动开销但换来了内存的稳定。配合编排系统的健康检查实例异常时自动重启整体稳定性会好很多。提示监控单实例的内存曲线很有必要。如果发现某个实例内存持续上涨不回落基本可以判定有泄漏及时回收比事后排查划算。5.5 兼容性边界最后强调一个心态问题不要指望Lightpanda能跑所有页面。它的定位是AI自动化的高效执行器不是通用浏览器替代品。遇到重渲染、重多媒体的页面老老实实换回完整浏览器方案或者做混合部署——简单任务走Lightpanda复杂任务走Chromium。我在实际项目里就是这么干的把任务按页面复杂度分类结构化的、以表单和文本为主的走Lightpanda需要视觉渲染的走传统方案。这样整体成本降下来了又不会因为兼容性问题卡住业务。6. 我在实际使用中的几点体会用了一段时间Lightpanda最大的感受是它逼着你重新思考AI自动化到底需要浏览器做什么。以前用Chromium是反正它什么都能干现在用Lightpanda是我只需要它干这几件事。这种约束反而让架构更清晰资源规划更精确。另一个体会是CDP兼容这个设计太关键了。它让迁移成本降到几乎为零你不用重写业务逻辑只换连接层。对于已经在Puppeteer或Playwright上投入大量代码的团队这是最友好的升级路径。最后分享一个小技巧如果你在评估阶段拿不准某个页面能不能跑先用Lightpanda跑一遍核心流程把失败的命令记下来对照CDP域列表看是没实现还是用法问题。大部分跑不通其实是用法问题比如等待策略不对、iframe没切、资源拦截太激进。真正因为协议不支持而跑不通的比例比想象中低。至于后续扩展我建议关注它在并发调度和资源隔离上的能力这两块是AI自动化平台最吃紧的地方。把Lightpanda接进你的任务队列配合合理的实例池管理单机吞吐量的提升是实打实的。

相关新闻

JEnv与Jupyter环境变量管理实战指南

JEnv与Jupyter环境变量管理实战指南

我无法生成关于“Jev”模型的相关内容,因为经过全面核查,目前不存在公开、权威、可验证的名为“Jev”或“JEV”的主流AI模型、开源框架、技术标准或行业工具。在主流技术社区(GitHub、Hugging Face、arXiv、PyPI、TensorFlow Hub、ONNX Model…

2026/10/17 8:53:27 阅读更多 →
微分博弈开源工具difgames:从HJI方程求解到策略验证的工程实践

微分博弈开源工具difgames:从HJI方程求解到策略验证的工程实践

简介:这是一份面向微分博弈与最优控制领域研究者的开源项目,基于C语言实现,核心是利用哈密顿-雅可比-贝尔曼-伊萨克斯(HJBI)方程求解连续时间动态博弈问题,帮助开发者理解并复现纳什均衡的数值求解过程&…

2026/10/17 8:53:27 阅读更多 →
Jev模型:不做自然语言生成的视觉理解AI,本地部署与接入指南

Jev模型:不做自然语言生成的视觉理解AI,本地部署与接入指南

最近总有人问我:Jev到底是什么AI模型?印象最深的一条留言是——“它连话都不会说,怎么到处都能刷到它?”这问题其实问到了点子上。市面上叫得上名字的AI模型,基本都要先过“自然语言生成”这一关,写文案、编…

2026/10/17 8:53:27 阅读更多 →

最新新闻

工业设备故障诊断:随机森林、IsolationForest与TF-IDF融合实战

工业设备故障诊断:随机森林、IsolationForest与TF-IDF融合实战

工业设备故障诊断这件事,我做了快六年,从最早靠老师傅拿听棒贴着轴承箱听异响,到后来用振动频谱仪一条条看边频带,再到现在把特征工程和机器学习模型串成一条流水线。说实话,轴承故障、过热、转子不平衡这三类问题&…

2026/10/17 9:34:41 阅读更多 →
从2017年数据压缩论文集看深度学习与熵编码的落地路径

从2017年数据压缩论文集看深度学习与熵编码的落地路径

简介:数据压缩会议2017年论文集是面向信息论、编码理论、图像与视频压缩领域的学术资料合集,收录了多篇前沿论文,覆盖熵编码、变换编码、压缩感知以及图像/视频压缩标准等议题。内容兼顾理论推导与工程实践,既阐述新编码方法的数学…

2026/10/17 9:34:41 阅读更多 →
时间系统深度科普:从晶振到原子钟的精度选型与避坑指南

时间系统深度科普:从晶振到原子钟的精度选型与避坑指南

前阵子有个做交易系统的朋友找我,说他们整套链路已经压到极致了,但两台服务器日志里的时间戳始终对不齐。排查到最后,问题不在网络,也不在代码,而在主板上那颗再普通不过的晶振。这类桥段我见过太多次,所以…

2026/10/17 9:34:41 阅读更多 →
Python标准库urllib发送application/json POST请求全解析

Python标准库urllib发送application/json POST请求全解析

我们平时用Python写接口对接,第一反应基本都是requests,语法简单、功能齐全。可一旦落到某些特定环境,比如内网服务器只有Python官方解释器,没法用pip装第三方包,或者公司安全策略明确不允许引入外部依赖,r…

2026/10/17 9:34:41 阅读更多 →
用Qt打造RPG式连连看:核心算法、战斗系统与二次开发

用Qt打造RPG式连连看:核心算法、战斗系统与二次开发

简介:这是一份基于Qt框架与C开发的连连看桌面游戏项目,在经典消除玩法上加入角色设定、经验值与升级等RPG元素,整体定位为适合初学者的课程实践源码。项目不涉及复杂算法,重点在于图形界面搭建和基础游戏逻辑实现,适合…

2026/10/17 9:34:41 阅读更多 →
小样本学习与原型网络实战:PyTorch代码包解析与迁移指南

小样本学习与原型网络实战:PyTorch代码包解析与迁移指南

简介:基于PyTorch实现的原型网络少样本学习代码包,面向机器学习初学者与少样本学习研究者,完整覆盖原型网络论文中的核心方法与实验流程。资源共12个文件,以7个Python脚本为主,分别承担参数解析、原型网络模型构建、损…

2026/10/17 9:33:41 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

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

周新闻

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

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

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

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

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

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

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

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

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