自动备份 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 事件](https://jekyllrb.com/docs/plugins/hooks/#built-in-hook-owners-and-events){:target=”_blank”} 有很多,这边只附上我用到的 site:pre_render 跟 post:pre_render
新增方式也很简单,只要在 ./_plugins 新增一个 Ruby 档案即可。

posts-lastmod-hook.rb 是原本就有的 Plugin
我想要几个「伪」动态内容功能,第一个是在个人资料下显示 Medium 追踪人数还有在页底显示页面内容最后更新时间。

在 ./_plugins 下建立了一个 zhgchgli-customize.rb :
#!/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: 新增:
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 的部署步骤:
# 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 标签是否有缺漏…等等
# 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



留言 · Comments