某日在掘金划水的时候,我突然想到一件事情
我在掘金发布了很多文章,发布之后保存在本地的原文件因我懒得整理,放在桌面上又碍事,所以顺手都删掉了,也就是说我写的文章仅在掘金有正版孤本,如果哪天 掘金倒闭了/掘金被删库跑路了/掘金被黑客rm -rf了/掘金交不起OSS费用被停机了,那我文章丢失的几率岂不是很大吗?
虽然我文章质量一般般,但好歹都是我花了时间写的,8行,一定要杜绝这个隐患,容灾备份走起
把这些文章保存下来倒也不难,直接点开每篇文章的编辑后台,一个个复制粘贴到本地,再把每篇文章里的图片下载下来,整理到文件夹中即可,但我当初在本地有原文件的时候我都懒得整理,现在还想让我从头复制粘贴新建文件不是难为我胖虎吗?
于是我想到,我直接写个浏览器插件,把这件事情自动化不就行了
先上效果图:
- 最终版本效果图可能会有所变化
禁止吐槽UI
插件支持 下载单篇文章 和 批量下载某作者所有文章 两个功能,可以下载自己的文章,也可以下载别人的文章,并且在下载文章的时候,会把文章中引用的图片资源给下载下来,并将图片在文章中的远程地址自动替换为本地路径
获取文章内容
我随便点进一篇文章,然后打开控制台看了下,发现已经有接口将文章详情的原始 markdown 字符串返回了,又发现接口入参只需要当前文章 id,又发现文章的 id 其实已经明文写在链接上的,简直天助我也,一个接口就能拿到所有数据
代码刷刷写完,然后随便找几篇文章测试下,没想到很快就遇到问题了,有的文章详情接口返回的文章markdown原文字段是空字符串,我一开始还以为掘金用了啥反作弊的技术,后来仔细分析后才发现并不是这样,应该是因为产品设计经历过多次迭代,早期发布的文章不会返回原始 markdown 字段让前端转,而是直接返回转化后 的 html 字符串
发现了问题就很好办了,文章详情内容还是返回的,只不过形式变了嘛,你既然是转化后的 html 字符串,那我再把你转回到 markdown 原文不就行了?
这种开源库还是很多的,我看了一圈最终选择了 turndown,那么反转译这件事只需要一句话就能搞定:
turndownService.turndown(html)
不过我又考虑到,反转译这种事情很难确保做到 100% 恢复原文,我下载自己的文章肯定希望能保证 100% 原模原样,哪怕只有一个逗号不一致也很难受
如果我想下载的文章是我自己的文章,那么我肯定能够进入编辑后台,编辑接口返回的肯定是原始 markdown 字符串,我只要下载这个内容就能保证 100% 还原了,于是跑去看了下,发现确实如此
而编辑接口需要一个 draft_id 的入参,巧的是,这个 draft_id 从上面的文章详情接口就能拿到
对于不属于自己的文章,肯定进不去编辑后台,那还是按照反转译的方法来,经过我的测试,反转译出来的内容基本上和原文没有多少区别
获取文章中图片
既然是下载,那么就算是文章中的图片也要下载下来,一来是真正起到保存到本地的目的,二来你以后想发表在其他平台也方便转存图片,三来如果掘金将来哪天给图片加了个防盗链,你可能就看不了了
从文章字符串中取出原文,正则匹配就行了,只不过因为有的文章详情接口返回的是 markdown 有的返回 html, 所以需要各写一个正则规则
匹配到图片链接后,再根据这些链接将对应的图片下载下来,然后还得将原文中图片的远程链接替换成下载到本地的图片文件路径,即将本地图片一一对应到文章中的位置,否则下载了一堆图片,总不能一个个肉眼对齐或者手动去替换吧?所以在处理的时候,需要考虑到这一点
下载文章
现在文章原文的 markdown 字符串已经有了,下一步需要保存到本地
将字符串转为二进制文件,使用到 Blob,将二进制文件保存到本地有两种方法
一种是使用 <a> 标签,借助 <a> 标签的 download 属性模拟浏览器下载,但这种的方法局限性是无法指定下载目录,只能下载到默认目录或者由用户一个个手动指定
而我想的是,最好能够以作者名作为文件夹名称,然后这个文件夹下再新建一个个子文件夹,这些子文件夹的名称就是每篇文章的名称,子文件夹下包含一个 .md格式的文章正文内容文件,和若干图片文件,这样会比较有规律,也方便存档,所以我选择借助浏览器插件暴露出来的 chrome.downloads.download 方法
var blob = new Blob([message.data.content], { type: 'text/x-markdown' })
chrome.downloads.download({
url: URL.createObjectURL(blob),
saveAs: false
})
下载图片
同样是借助 chrome.downloads.download 方法将图片下载到本地,这一步需要关注的是,因为要在文章原文中替换图片远程路径,所以需要在下载开始之前确定好图片名称
图片的扩展后缀有很多种,例如 .jpg、.png、.gif、.svg,但好在在请求的响应头的 content-type 字段会告知图片 MIME 是什么,所以也是可以确定后缀的
一般来说,当你在页面上下载一个文件的时候,浏览器会弹出一个弹窗,告诉你要下载的文件信息以及让你确定是否真的下载,考虑到一篇文章中可能存在多张图片,再加上文章原文文件,如果一个个点确定也太麻烦了,若是再换成批量下载,那搞不好要点个上百下,不友好
看了下文档,chrome.downloads.download有个 saveAs的参数,按照 文档 上的说法,如果此值被指定为 false,那么在下载的时候就不会弹出下载确认弹窗了,但是我试了下发现根本行不通,这个参数无论加不加无论设置成什么值,浏览器的下载弹窗该弹还是要弹的,网上搜了下,发现这是一个至今未修复的 陈年老bug
所以这条路是走不通了,不过还可以通过其他方式解决一下
打开 chrome 浏览器的设置,找到 下载内容这一项(或者直接在浏览器打开 chrome://settings/?search=%E4%B8%8B%E8%BD%BD%E5%86%85%E5%AE%B9),然后关闭 下载前询问每个文件的保存位置 的开关,这样在下载的时候,就不会弹出下载对话框了
如果你使用本插件批量下载文章,一定要关掉上述开关,否则你浏览器可能会一瞬间弹出几十上百个确认弹窗
当然,考虑到你可能就不想下载图片,所以插件也提供了主动取消下载图片的功能,由你自己抉择
小结
插件逻辑流程大致如下:
使用此插件下载掘金文章到本地,再配合我写的另外一个插件 MdPreview 实现浏览器查看本地 markdown 文件,全程浏览器操作效果顺滑
这个插件的技术难度并没有多少,找准实现插件的流程以及各种case的处理占了大多数工作,我本意是想写个插件快速完成将文章下载到本地的工作,然而最后完整地实现插件所花费时间,已经足够我把自己的文章手动一个个下载到本地十遍了,本末倒置本末倒置(手动狗头)
我在我的电脑上进行了较为完全的测试,基本上没啥问题,但是我不知道在你的电脑上是否正常,因为这种类似于爬虫的东西出错是很正常的(边界值太多了),所以如果你用了之后发现出问题了,那么可以给我提个 issue,或者也可以直接提 PR
目前已知的问题是,外链进来而非直接上传到掘金的图片可能会因为跨域问题导致下载不成功,这个纯前端没办法解决,不过据我测试结果显示,外链图片比例是很少的,所以影响不大
由于打包成 ctx 文件需要注册账号,还要上传,好像还要交坑位费,不想那么麻烦,所以没有上传到 chrome extension store,各位到Github上自行下载源码文件后,进行离线安装即可(可以参考 为Chrome浏览器离线安装添加插件)
测试的时候发现了三件有意思的事情
- 诸位文章的标题一个比一个夸张
- 很多人对于
markdown语法的掌握程度比我还菜- 有的人文章没写多少
diao图倒是一堆