# 剪藏网页到本地的轻量化思路

> 本文公开全文。

原文：https://utgd.net/article/8668

《一种将线上内容精简格式后保存到本地的方法》 源自 Hum 和我的一系列讨论，我们意识到，互联网上的文章都是不可靠的。我们主动提供保存网页的工具，在这个随处都是内容订阅的时代，或许有幸被戴上屠龙勇士的桂冠；但是没有人能够保证，勇士自己不会成为下一个魔王。所以从一开始，我就不会要求任何人信任某个平台（包括我们）；相反，我直接把屠龙的武器分发到每一个自由民手中。

我在 《剪藏网页到本地的自动化思路（以 Keyboard Maestro 为例）》 中将 Hum 的方法转为自动化流程。不过，该方案仍然和 DEVONthink 相连甚密，对于多数读者来说都是一个不小的门槛——遑论不用 macOS 的人了。尽管配套工具是我编写的，但在制作完成后，我很快开始寻找更加轻便的方法，遂有此文。

## 反思 DEVONthink 剪藏思路

首先就是贵。DEVONthink 要一千多块钱，恐怕没有急迫需求的人是买不下手的。而更根本的问题在于，DEVONthink 毕竟太重，需要把网页源码下载到本地，然后渲染回网页，再删删减减一番，最后导出为 PDF。这时候，我会怀念 Evernote 那轻巧的操作，不用离开浏览器，点几下就能剪藏好页面——尽管虽然经常失效，这也是我自己研究工具的起因之一。

打个比方，DEVONthink 之于网页剪藏，正如 Word 之于文字编辑，或者 Photoshop 之于图片处理。你基本可以用 Word 做出任何花哨的排版，或者用 Photoshop 创造出令人惊叹的图片，但这一般都是个大工程。有些人——包括我——更喜欢轻便的工具，而 DEVONthink 则适合作为兜底的备用工具。

另一个问题——并不全是 DEVONthink 的缘故——则是我和 Hum 在偏好上的分歧。他喜欢 Safari 阅读模式之干净白练，宛如纸张；而我更希望保留网页原始排版（只要别太烂），因为网页排版也是文章的一部分。The Verge 排版刺激视觉，各种野兽派配图和赛博朋克配色，旨在挑动读者神经；Long Now 博客试图让读者沉下心来阅读，两者截然不同。如果都用同一个排版，尽管整洁，但也丢失了文章的个性。

一句话，DEVONthink 是非常可靠的兜底方案，并且能够一股脑儿地解决问题；但它的用起来有些笨重，而用阅读模式导出的文章又像一个模子里出来的。我决定另寻出路，直接在浏览器里解决剪藏问题，并且保留网页原始排版。以下方案并不能代替 DEVONthink，但可能是一个有用的补充。

## 另一套网页剪藏方案

DEVONthink 一次性解决了两个难题，第一是清理网页上的繁杂元素，比如二维码和广告；第二则是导出不分页的 PDF，以免把文章中的配图拦腰截断。别辟门户之后，首先要重新解决这两个问题；此外，才是针对浏览器环境做一点修修补补。

### 删除多余元素

就删除多余元素而言，DEVONthink 的解决方案是提供一个完整的 HTML 编辑环境——当然在 Hum 的整个流程中，还要先用阅读模式过滤一遍——以便你像编辑 Word 文档一样轻松删除不需要的信息。在浏览器中搭建一个 HTML 编辑器显然很困难，但是有一个取巧的方法：直接删掉 HTML 元素。熟练的网页开发者固然知道在调试界面中删除元素，而我发现了一个更方便的 Bookmarklet，可以把光标变成剪刀，径直在页面上删除多余内容（但我找不到这个 Bookmarklet 的原始作者，知道的读者请告知我）。诸如悬浮目录、返回按钮、广告横幅或页面底部的推广二维码，凡是不乐意在 PDF 中看到的，均可点击删除。

你可以把下方代码收藏为书签（或先随便创建一个书签，然后改其链接为下方代码），日后需要剪除网页元素时，点一下即可。感谢读者 Flez 提供的改进版本，以下内容目前是 Flez 的版本。

```
javascript: (function%20()%20%7B%20%20%20var%20isIe%20%3D%20false%3B%20%20%2F*%40cc_on%20isIe%3Dtrue%3B%20%40*%2F%20%20function%20fe(a%2C%20fn)%20%7B%20%20%20%20%20var%20i%2C%20l%20%3D%20a.length%3B%20%20%20%20%20for%20(i%20%3D%200%3B%20i%20%3C%20l%3B%20i%2B%2B)%20%7B%20fn(a%5Bi%5D)%3B%20%7D%20%20%20%7D%3B%20%20%20function%20ae(el%2C%20n%2C%20fn%2C%20ix)%20%7B%20%20%20%20%20function%20wfn(ev)%20%7B%20%20%20%20%20%20%20var%20el%20%3D%20(isIe%20%3F%20window.event.srcElement%20%3A%20ev.target)%3B%20%20%20%20%20%20%20if%20(n%20%3D%3D%20%27click%27)%20%7B%20%20%20%20%20%20%20%20ev.stopPropagation()%3B%20%20%20%20%20%20%20%20ev.preventDefault()%3B%20%20%20%20%20%20%20%7D%20%20%20%20%20%20if%20(ix%20%7C%7C%20!el.xmt)%20%7Bfn(el)%3B%7D%20%20%20%20%20%7D%20%20%20%20%20if%20(isIe)%20%7B%20%20%20%20%20%20%20n%20%3D%20%27on%27%20%2B%20n%3B%20el.attachEvent(n%2C%20wfn)%3B%20%20%20%20%20%7D%20else%20%7B%20%20%20%20%20%20%20var%20isUseCapture%20%3D%20(n%20%3D%3D%20%27click%27)%3B%20%20%20%20%20%20el.addEventListener(n%2C%20wfn%2C%20isUseCapture)%3B%20%7D%20%20%20%20%20if%20(!el.es)%20el.es%20%3D%20%5B%5D%3B%20%20%20%20%20el.es.push(%20%20%20%20%20%20function%20()%20%7B%20%20%20%20%20%20%20%20%20if%20(isIe)%20%7B%20el.detachEvent(n%2C%20wfn)%3B%20%7D%20%20%20%20%20%20%20%20%20else%20%7B%20el.removeEventListener(n%2C%20wfn%2C%20isUseCapture)%3B%20%7D%20%20%20%20%20%20%20%7D)%3B%20%20%20%20%20el.re%20%3D%20function%20()%20%7B%20fe(el.es%2C%20function%20(f)%20%7B%20f()%20%7D)%3B%20%7D%3B%20%20%20%7D%3B%20%20function%20sce(el)%20%7B%20%20%20%20%20var%20oldclick%20%3D%20el.onclick%2C%20oldmu%20%3D%20el.onmouseup%2C%20oldmd%20%3D%20el.onmousedown%3B%20%20%20%20%20el.onclick%20%3D%20function%20()%20%7B%20return%20false%3B%20%7D%3B%20%20%20%20%20el.onmouseup%20%3D%20function%20()%20%7B%20return%20false%3B%20%7D%3B%20%20%20%20%20el.onmousedown%20%3D%20function%20()%20%7B%20return%20false%3B%20%7D%3B%20%20%20%20%20el.rce%20%3D%20function%20()%20%7B%20el.onclick%20%3D%20oldclick%3B%20el.onmouseup%20%3D%20oldmu%3B%20el.onmousedown%20%3D%20oldmd%3B%20%7D%3B%20%20%20%7D%20%20%20if%20(!window.r_)%20window.r_%20%3D%20%5B%5D%3B%20%20%20var%20r%20%3D%20window.r_%3B%20%20%20var%20D%20%3D%20document%3B%20%20%20ae(D.body%2C%20%27mouseover%27%2C%20function%20(el)%20%7B%20el.style.backgroundColor%20%3D%20%27%23ffff99%27%3B%20sce(el)%20%7D)%3B%20%20%20ae(D.body%2C%20%27mouseout%27%2C%20function%20(el)%20%7B%20el.style.backgroundColor%20%3D%20%27%27%3B%20if%20(el.rce)%20el.rce()%3B%20%7D)%3B%20%20%20ae(D.body%2C%20%27click%27%2C%20function%20(el)%20%7B%20%20%20%20el.style.display%20%3D%20%27none%27%3B%20%20%20%20%20r.push(el)%3B%20%20%20%7D)%3B%20%20%20function%20ac(p%2C%20tn%2C%20ih)%20%7B%20var%20e%20%3D%20D.createElement(tn)%3B%20%20%20%20%20if%20(ih)%20e.innerHTML%20%3D%20ih%3B%20%20%20%20%20p.appendChild(e)%3B%20%20%20%20%20return%20e%3B%20%20%20%7D%20%20%20var%20p%20%3D%200%3B%20%20%20var%20bx%20%3D%20ac(D.body%2C%20%27div%27)%3B%20%20%20bx.style.cssText%20%3D%20%27position%3A%27%20%2B%20(isIe%20%3F%20%27absolute%27%20%3A%20%27fixed%27)%20%2B%20%27%3Bpadding%3A2px%3Bbackground-color%3A%2399FF99%3Bborder%3A1px%20solid%20green%3Bz-index%3A9999%3Bfont-family%3Asans-serif%3Bfont-size%3A10px%27%3B%20%20%20function%20sp()%20%7B%20%20%20%20%20bx.style.top%20%3D%20(p%20%26%202)%20%3F%20%27%27%20%3A%20%2710px%27%3B%20%20%20%20%20bx.style.bottom%20%3D%20(p%20%26%202)%20%3F%20%2710px%27%20%3A%20%27%27%3B%20%20%20%20%20bx.style.left%20%3D%20(p%20%26%201)%20%3F%20%27%27%20%3A%20%2710px%27%3B%20%20%20%20%20bx.style.right%20%3D%20(p%20%26%201)%20%3F%20%2710px%27%20%3A%20%27%27%3B%20%20%20%7D%20sp()%3B%20%20%20var%20ul%20%3D%20ac(bx%2C%20%27a%27%2C%20%27%20Undo%20%7C%27)%3B%20%20%20ae(ul%2C%20%27dblclick%27%2C%20function%20()%20%7B%20var%20e%20%3D%20r.pop()%3B%20if%20(e)%20e.style.display%20%3D%20%27%27%3B%20%7D%2C%20true)%3B%20%20%20var%20ual%20%3D%20ac(bx%2C%20%27a%27%2C%20%27%20Undo%20All%20%7C%27)%3B%20%20%20ae(ual%2C%20%27dblclick%27%2C%20function%20()%20%7B%20var%20e%3B%20while%20(e%20%3D%20r.pop())%20e.style.display%20%3D%20%27%27%3B%20%7D%2C%20true)%3B%20%20%20var%20ml%20%3D%20ac(bx%2C%20%27a%27%2C%20%27%20Move%20%7C%27)%3B%20%20%20ae(ml%2C%20%27dblclick%27%2C%20function%20()%20%7B%20p%2B%2B%3B%20sp()%3B%20%7D%2C%20true)%3B%20%20%20var%20xl%20%3D%20ac(bx%2C%20%27a%27%2C%20%27%20Exit%20%27)%3B%20%20%20ae(xl%2C%20%27dblclick%27%2C%20function%20()%20%7B%20D.body.re()%3B%20bx.parentNode.removeChild(bx)%3B%20%7D%2C%20true)%3B%20%20%20fe(%5Bbx%2C%20ul%2C%20ml%2C%20xl%2C%20ual%5D%2C%20function%20(e)%20%7B%20e.style.cursor%20%3D%20%27pointer%27%3B%20e.xmt%20%3D%201%3B%20%7D)%3B%20%7D)()
```

诚然，编辑页面元素的 Bookmarklet 只能删而不能改，更不能增加图文，不如 DEVONthink 那么全能。但这一方案胜在小巧，在网页上随手点几下，原本杂乱的页面就变得清爽怡人。此外，Bookmarklet 不限于特定浏览器，Chrome、Firefox 和 Edge 等主流浏览器的用户均可受惠——甚至，手机也可以用 Bookmarklet 清理网页，配合 iOS 近几个系统的全网页截图，很容易导出流畅干净的网页，试想在 iPad 上阅读文章，顺手编辑一下就能导出，如同自由处置买回家的杂志一样，令人愉快。

### 导出连续的 PDF

至于第二个问题 ，即导出不断页的、连续的 PDF（DEVONthink 谓之 Continous PDF）。部分读者可能尚未读过 Hum 的文章，这里解释一下，所谓断页就是分页的 PDF 在遇到图片时，可能会将其直接截成两端，塞在不同的页面上，很是粗鲁。

事实上，Safari 本身就可以导出连续的 PDF。通常情况下，直接打印页面得到的 PDF 固然是分页法的；而使用“Export as PDF…”选项导出 PDF，则可以得到连续的 PDF。这一选项类似于 iOS 设备上的全网页截图，即在 Safari 中按下截图后，旋即点击缩略图，就能看到的“整页”选项。

导出连续 PDF 的好处，不仅在于能够避免图片被截断，还可以最大程度保留原网页的排版。很多页面的题图（banner）非常养眼，但 Safari 的阅读模式不一定能够抓取它，或者不认为它是文章的一部分，因此用阅读模式导出的 PDF 多多少少缺了一些原文的风味。而导出连续的 PDF，通常可以连同题图等特殊部分一起导出。此外，很多人阅读技术类文章时希望配有代码高亮，这也是阅读模式的软肋，而直接导出 PDF 则不存在破坏原网页高亮效果的担忧。偶尔遇到不能正常获得题图的，很可能是原网页将其设置为背景，因此在打印时勾选背景选项即可；部分网页会提示这一细节，避免打印时丢失背景图片。许多英文博客就把题图当作背景图处理，因此不能直接打印，阅读模式亦不逮，碰上这些空白图片，试试打印背景往往会有惊喜。

不过，导出连续 PDF 也不是完美的方案，其旨在保留原网页视觉风格，但在其他方面有所欠缺。显而易见的就是体积占用，如果直接打印的分页 PDF 只有 2mb（下图左），那么连续 PDF 通常就有两三倍的大小（下图中），要是再带上背景图（下图右），占用就更多一些。此外，如果页面太长，连续的 PDF 恐怕也不便于翻阅和加书签——毕竟只有一页，连“翻”和加书签的动作都做不到——要查找资料，就得依靠搜索，或干脆上下来回滚动。其中利弊，还是交给读者自己权衡。

## 后记

就我个人而言，DEVONthink 一直在后台运行，时不时就要打开一下，其实并没有必要批评 DEVONthink 剪藏方案多么笨重。改良的初衷其实是希望保留网页特色。最初的想法，是在 DEVONthink 剪藏的基础上增添一些花样，比如加上网页原有配色，或者插入网站图标……

差不多实现了之后，最初的剪藏工具变得越发庞大，而且花样一多，也就不再鲁棒，频频遇到无法正常剪藏的网页——和我不得不割爱的 Instapaper 一样。此时我才决定换个方向，舍弃 DEVONthink，转而直接在网页上操作。此时我发现了前文介绍的网页编辑 Bookmarklet，又正好读到 Eclectic Light 的 这篇 文章，对 Safari 导出 PDF 的机制有了更多认识——之后的事情，就很简单了。

🛍 我撰写的付费栏目《信息管理，文件为本位的方案》正在 UNTAG 售卖，对本文话题有进一步讨论，欢迎选购。

🔗 付费栏目链接

---

[前往标准阅读页](https://utgd.net/article/8668?audience=web)
