前阵子调一个老项目的接口时,我在CSDN上翻到了一篇正好对症的文章。那篇文章有完整的实现代码,我当时的想法很简单:先存下来,等会去工位旁边的打印机打出来慢慢看。结果Ctrl+P一按,出来的PDF简直没法看——代码块只显示一半,右下角盖着一个水印,导航栏和推荐位那些乱七八糟的东西全挤在纸面上。我当时就想,得给自己做一个小工具,把"打开文章→复制代码→手动整理→打印"这套流程缩成一键。于是有了这个书签脚本:在CSDN文章页点一下收藏夹里的按钮,自动展开全部代码、清掉水印和干扰模块、调整好打印样式,最后直接弹起打印对话框。这篇文章就是把这个小工具的完整思路、代码和踩坑过程分享出来,给同样对"保存技术文章PDF"这件事有执念的朋友参考。
1. 从一次失败的"保存网页"说起:CSDN文章到底卡在哪
1.1 三件烦心事:折叠代码、水印和乱排的布局
技术文章的打印痛点,说来说去就三个,而且这三个几乎是CSDN这类内容平台的通病。
第一个是代码折叠。CSDN 的长篇文章为了首屏阅读体验,默认会把超过一定行数的代码块折叠起来,只露出开头几行,要阅读全文得手动点一下"展开"。如果只是浏览器里看倒还好,问题是打印的时候浏览器只渲染当前DOM状态,你没点开的部分,打印出来就是不完整的。一篇文章里十来个代码块,每个都要手动点开,漏掉一个就得重新打印。
第二个是水印。CSDN 在打印或者截图时会在文章区域叠加一层水印,通常是右下角的一个半透明标志,有时候是博主ID。这个水印在屏幕上看不太显眼,但落到纸面上或者转成PDF之后,正好压在代码区上,特别影响阅读。更麻烦的是它的实现方式不一定只有一种,有些是页面里的DOM节点,有些是canvas画上去的,还有些是打印样式表里通过伪元素动态生成的。处理思路完全不同。
第三个是打印布局。CSDN 页面元素非常多:顶栏、侧边栏、博主信息卡、相关推荐、评论列表、底部工具条……直接 Ctrl+P 打印,这些模块全都会被塞进 PDF,一篇文章能打出七八页,真正有价值的正文反而被挤得支离破碎。与此同时,页面自带的 CSS 对屏幕媒体做了大量适配,但到了打印媒体上,这些适配基本失灵。
三件事叠在一起,"打印CSDN文章"这个动作就从一个简单需求变成了一个需要技术处理的问题。
1.2 用Ctrl+S存网页,为什么问题更严重
很多人打印之前会先尝试把网页保存到本地,这个思路听起来没毛病,但在CSDN上效果更差。
静态保存下来的HTML,只是把当前页面DOM的某一瞬间快照存了下来。CSDN 这类动态站点,很多代码内容是靠异步请求加载的,你 Ctrl+S 存下来的页面里可能压根没有完整代码,只有一段"加载中"的占位符。再加上现代浏览器默认会把JS执行状态和CSS一并保存,如果原页面里有用JS动态渲染的水印层,保存下来的本地副本里也一样存在。你本地打开这个HTML,再打印,还是会踩一遍上面说的那些坑。
更隐蔽的问题是异步资源的引用。保存下来的HTML文件依赖的CSS、JS、图片如果仍指向原站点域名,断网的时候打开就是一堆样式全丢的"素颜"页面,打印出来的效果比在线打印还要差。
1.3 这工具的适用场景和不适用场景
在动手写脚本之前,先把这个工具的边界想清楚。它适用的场景是这样的:你要把CSDN上一篇技术文章存成PDF,或者打印出来,且这篇文章是公开可访问的,你只是做本地存档、离线阅读或批注。它不适用的情况也有:你想批量下载别人的整个专栏,或者想在转载时去掉水印署上自己的名字,这超出了"个人工具"的合理范围,我也不建议这么干。
所以我做的这个"清洗打印"脚本,定位非常明确:它只是一个运行在浏览器里的前端页面清理工具,不碰任何接口、不做登录态相关的处理、不搞破解,纯粹解决"把网页变成一份干净文档"这个需求。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 打印前页面清洗:预览清洗脚本的核心逻辑与关键代码
2.1 为什么我用书签脚本而不是浏览器扩展
一开始我也考虑过写一个浏览器扩展,毕竟扩展有完整的API权限,可以做更多事情。但仔细一琢磨,对这个需求来说扩展是杀鸡用牛刀。
书签脚本(bookmarklet)的核心优势是零安装、零权限、跨浏览器通用。一个 javascript: 开头的短链接,收藏到书签栏,点击就在当前页面执行JS,不需要去扩展商店审核,不需要打包CRX,Edge、Chrome、Firefox通吃。对个人工具来说这是最省事的形态。
当然它也有缺点:书签脚本受同源策略限制比较死,不能跨域去乱动其他站点的内容;另外它的执行时机完全取决于用户什么时候点击,不像扩展可以挂在页面加载事件上自动触发。这个缺点我会在后面的"自动清洗"方案里用用户脚本管理器来弥补。
2.2 展开折叠代码:先看DOM,再找按钮
展开代码这个动作,细想一下其实有两个层次。
第一层次是页面里有一个明确的"展开"按钮,点了之后才会把完整代码渲染出来。这种情况最直接的处理方式就是遍历页面找到所有这类按钮,逐个点击。但CSDN页面里的按钮类名并不完全固定,不同年份的文章模板用的HTML结构还不一样,所以我选了一个更鲁棒的方案:不光找"展开"按钮,还把 aria-expanded="false" 的元素、data-toggle="collapse" 这类折叠标记一并扫描。之所以这么干,是因为代码折叠的实现方式在历史上反复变过,靠单一选择器最容易翻车。
第二层次是文章正文被限高遮罩。CSDN 有些文章开头会有一段"阅读全文"的遮罩,挡住后面的内容,点击后遮罩消失、正文展开。这也是一个需要处理的 DOM 节点,但它不是按钮而是遮罩层,处理方式不是点击而是直接移除。
下面是展开部分的代码逻辑,这个我实测下来基本能覆盖常见页面:
javascript复制// 展开折叠代码块:优先模拟点击,再兜底移除遮罩
funct
