自动备份 Medium 文章到 Github Pages|Jekyll 静态备份与部署全攻略
透过自制工具将 Medium 文章自动转成 Markdown,并利用 Jekyll Chirpy Theme 部署到 Github Pages,解决文章遗失风险与滚动卡顿问题,实现免费且稳定的持续备份与客制化展示。
Click here to view the English version of this article.
點擊這裡查看本文章正體中文版本。
基于 SEO 考量,本文标题与描述经 AI 调整,原始版本请参考内文。
自动备份 Medium 文章到 Github Pages (Jekyll) 的那些事
个人 Medium 文章备份镜像站搭建、维护、升级、客制化的一些纪录
前言
经营 Medium 来到了第 6 年,文章总数在去年突破 100 篇;随著经营时间越长、文章越多,越怕哪天 Medium 突然关闭或是帐号异常造成所有文章心血付之一炬,有的文章含金量不高道无妨,但更多的是记录技术架构跟当时的解题思维,我时常也会回来看之前写的文章,重新复习知识;另外后面几年也开始记录出国旅游游记,都是回忆并且流量表现不错;这些内容一但遗失就不可能再重新撰写了。
自行开发备份工具
我习惯都是直接在 Medium 平台上撰写文章,没有自己的备份,因此在 2022 年过年期间花时间开发了一个 Medium 文章下载&转换成 Markdown 文件(包含文章图片、文章内嵌的程式码…等内容) 的工具 — ZMediumToMarkdown :
并延伸使用此工具将下载下来的 Markdown 使用 Jekyll (Chirpy Theme) 做为静态备份镜像网站部署在 Github Pages 上 — https://zhgchg.li/
那时候把这整套整合成一个 Github Template Repo 给有同样需求的朋友可以快速部署使用 — ZMediumToJekyll ,在此之后(2022),我就没有再更新过 Jekyll (Chirpy Theme) 的版本跟设定了; ZMediumToMarkdown 持续有在维护,偶尔会发现格式解析错误就会立刻修正,目前趋于稳定。
那时候使用的 Jekyll (Chirpy Theme) 版本是 v5.x 没有太大的问题,该有的功能也都有(e.g. 置顶、分类、标签、封面图、留言…);只有在画面滚动时很常会出现无法滚动问题,但是在滑个几下又正常,一个操作体验缺憾,曾经尝试升级到 v6.x 还是有、回报给官方也没得到回应;再加上随著版本提升升级会遇到的冲突就越多,因此后来完全放弃升级这个念头。
近期才下定决心要解决 Jekyll (Chirpy Theme) 问题、升级版本、顺手重新优化快速部署工具 ZMediumToJekyll 。
New! medium-to-jekyll-starter 🎉🎉
medium-to-jekyll-starter.github.io
我将 Jekyll (Chirpy Theme) 最新版 v7.x 加上我的 ZMediumToMarkdown Medium 文章下载转换工具重新整合成新的 — medium-to-jekyll-starter.github.io Github Template Repo。
大家可以直接使用这个范本 Repo 快速设定搭建自己的 Medium 镜像内容备份网站, 一次设定永久持续自动备份、部署在 Github Pages 上完全免费 。
手把手设定教学请参考此篇文章: https://zhgchg.li/posts/medium-to-jekyll/
成果
上面的所有文章都是 **自动 从我的 Medium 下载所有内容&转换成 Markdown 格式&重新上传。*
附上随便一篇文章的转换成果作为比较范例:
升级后没再出现滚动卡住的问题了,借由这次升级也多加上了客制化动态内容 (显示 Medium 追踪人数)。
一些技术纪录
Jekyll (Chirpy Theme) 在 Github Pages 上的部署设定方式主要是直接参考官方 Start Repo:
上个月也参考这个专案的方式,做了一个新的开源专案 — Linkyee 开源版的 Link Tree 个人连结页面。
Jekyll 客制化方式 (1) — Override HTML
Jekyll 是一套很强大的 Ruby 静态内容网站生成引擎, Jekyll (Chirpy Theme) 只是一套基于 Jekyll 的主题,比较过其他主题还是 Chirpy Theme 最有质感跟操作体验优异、功能俱全。
Jekyll 的页面具有继承性,我们可以在 ./_layouts
新增 与 Jekyll 相同的页面档案名 ,引擎在产生网站内容时就会使用你自订的页面内容取代掉原本的。
例如我希望在每个文章页末尾加上一行文字,我先把原本的文章页面档案( post.html )复制出来,放到 ./_layouts
目录下:
使用编辑器打开 post.html 在相应的位置加上文字或客制化,重新部署网站就能看到客制化结果。
也可以建立一个 ./_include
目录,放一些想要共用的页面内容档案:
然后再 post.html
中我们就可以直接使用 {% include buymeacoffee.html %}
引入刚档案的 HTML 内容重复使用。
复写 HTML Layout 档案的优点是 100% 客制化,页面内容、排版要怎么呈现都可以随意调整;缺点是这次在升级的过程就会遇到冲突或是预期外结果,要自己重新检视一次客制化的内容。
Jekyll 客制化方式 (2) — Plugin
第二种方式是使用 Plugin 中的 Hook 方法,在 Jekyll 产生静态内容阶段注入自己想要的客制化内容。
Built-in Hook Owners and Events
Hook 事件 有很多,这边只附上我用到的 site:pre_render
跟 post:pre_render
新增方式也很简单,只要在 ./_plugins
新增一个 Ruby 档案即可。
posts-lastmod-hook.rb 是原本就有的 Plugin
我想要几个「伪」动态内容功能,第一个是在个人资料下显示 Medium 追踪人数还有在页底显示页面内容最后更新时间。
在 ./_plugins
下建立了一个 zhgchgli-customize.rb
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env ruby
#
require 'net/http'
require 'nokogiri'
require 'uri'
require 'date'
def load_medium_followers(url, limit = 10)
return 0 if limit.zero?
uri = URI(url)
response = Net::HTTP.get_response(uri)
case response
when Net::HTTPSuccess then
document = Nokogiri::HTML(response.body)
follower_count_element = document.at('span.pw-follower-count > a')
follower_count = follower_count_element&.text&.split(' ')&.first
return follower_count \\|\\| 0
when Net::HTTPRedirection then
location = response['location']
return load_medium_followers(location, limit - 1)
else
return 0
end
end
$medium_url = "https://medium.com/@zhgchgli"
# could also define in _config.yml and retrieve in Jekyll::Hooks.register :site, :pre_render do \\|site\\| site.config
$medium_followers = load_medium_followers($medium_url)
$medium_followers = 1000 if $medium_followers == 0
$medium_followers = $medium_followers.to_s.reverse.scan(/\d{1,3}/).join(',').reverse
Jekyll::Hooks.register :site, :pre_render do \\|site\\|
tagline = site.config['tagline']
followMe = <<-HTML
<a href="#{$medium_url}" target="_blank" style="display: block;text-align: center;font-style: normal;/* text-decoration: underline; */font-size: 1.2em;color: var(--heading-color);">#{$medium_followers}+ Followers on Medium</a>
HTML
site.config['tagline'] = "#{followMe}";
site.config['tagline'] += tagline;
meta_data = site.data.dig('locales', 'en', 'meta');
# only impletation in en, could impletation to all langs.
if meta_data
gmt_plus_8 = Time.now.getlocal("+08:00")
formatted_time = gmt_plus_8.strftime("%Y-%m-%d %H:%M:%S")
site.data['locales']['en']['meta'] += "<br/>Last updated: #{formatted_time} +08:00"
end
end
原理是注册一个 Hook 在网站 Render 前,对 config 中的
tagline
个人资料下方介绍内容区块,多塞上 Medium 追踪人数显示 HTML。Medium 追踪人数会在每次执行都去爬取拿到最新数字
页底最后更新时间逻辑也差不多,就是对 locales->en->meta 在产生网站时多塞上最后更新时间字串
补充如果是 Hook 文章产生前,可以拿到 Markdown、Hook 文章产生后,可以拿到产生后的 HTML
储存后可以先在本机下 bundle exec jekyll s
测试结果:
用浏览器打开 127.0.0.1:4000
查看结果。
最后在 Github Pages Repo 上的 Actions 加上排程定时自动重新产生网站,就完成了:
在 Jekyll (Chirpy Theme) Repo 专案中的 Actions 找到「 pages-deploy.yml
」在 on:
新增:
1
2
schedule:
- cron: "10 1 * * *" # 每天 UTC 01:10 自动执行一次, https://crontab.guru
Plugin 的优点是可以达到动态内容效果(排程更新内容)、不影响网站架构不会在升级时遇到冲突;缺点就是能调整的内容、显示位置有局限。
Jekyll (Chirpy Theme) v7.x 后的 Github Pages 部署问题
除了网站架构的调整外,v.7.x 的部署脚本也有改变;移除了原本的 deploy.sh 部署脚本,直接使用 Github Actions 的部署步骤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# build:
# ...
- name: Upload site artifact
uses: actions/upload-pages-artifact@v3
with:
path: "_site${{ steps.pages.outputs.base_path }}"
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
但是我在部署的过程遇到了问题:
Uploaded artifact size of 1737778940 bytes exceeds the allowed size of 1 GB
因为我的网站内容太大了,导致 Upload Artifact 失败;但是之前的部署脚本是可以的,所以只好退回去用原本的 deploy.sh + 注解掉上面这一段 。
Github Pages 部署时 Test Site 步骤一直不通过
Jekyll (Chirpy Theme) 部署有一个步骤是 Test Site 自检测网页内容是否正确,例如连结是否正常、HTML 标签是否有缺漏…等等
1
2
3
4
5
6
7
8
9
# build:
# ...
- name: Test site
run: \\|
bundle exec htmlproofer _site \
\-\-disable-external \
\-\-no-enforce-https \
\-\-ignore-empty-alt \
\-\-ignore-urls "/^http:\/\/127.0.0.1/,/^http:\/\/0.0.0.0/,/^http:\/\/localhost/"
我自己多加了 --no-enforce-https
--ignore-empty-alt
忽略 https、html tag没有 alt 的检查, 忽略这两条让检查通过(因为暂时无法去改内容) 。
htmlproofer 的 CLI 指令官方文件没有提,翻了好久才在某个 Issue 的 Comment 找到规则:
https://github.com/gjtorikian/html-proofer/issues/727#issuecomment-1334430268
其他文章补充
有任何问题及指教欢迎 与我联络 。
本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。