記事

自動バックアップ Medium 記事を Github Pages|Jekyllで効率的に管理する方法

Mediumの記事を自動でGithub Pagesにバックアップし、Jekyllでサイトを構築・カスタマイズする手順を解説。個人ブログのデータ保護と継続的な更新を実現し、管理負担を大幅に軽減します。

自動バックアップ Medium 記事を Github Pages|Jekyllで効率的に管理する方法

本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。

記事一覧


Mediumの記事をGithub Pages(Jekyll)に自動バックアップする話

個人 Medium 記事のバックアップミラーサイト構築、運用、アップグレード、カスタマイズの記録

はじめに

Medium を運営して6年目になり、記事数は昨年100本を超えました。運営期間が長くなり記事が増えるほど、ある日突然 Medium が閉鎖されたりアカウントが異常になって全記事が消えてしまうのが怖くなります。価値の低い記事もありますが、多くは技術構成や当時の問題解決の考え方を記録したもので、私は時々過去の記事を読み返して知識を復習しています。また、ここ数年は海外旅行の記録も始めており、思い出としても流入も好調です。これらの内容が失われると、二度と書き直せません。

自作のバックアップツール開発

私は普段Mediumプラットフォーム上で直接記事を書いており、自分のバックアップを持っていませんでした。そこで2022年の旧正月期間に、Mediumの記事をダウンロードしてMarkdownファイル(記事の画像や埋め込みコードなどを含む)に変換するツール ZMediumToMarkdown を開発しました。

そして、このツールでダウンロードした Markdown を Jekyll (Chirpy Theme) を使って静的バックアップミラーサイトとして Github Pages にデプロイしました — https://zhgchg.li/

<https://zhgchg.li/>{:target="_blank"}

https://zhgchg.li/

その当時、この一連の流れを同じニーズを持つ友人がすぐにデプロイできるように、Githubのテンプレートリポジトリとしてまとめました — ZMediumToJekyll。それ以降(2022年)、Jekyll (Chirpy Theme) のバージョンや設定は更新していません。ZMediumToMarkdown は継続的にメンテナンスしており、フォーマット解析の誤りを発見するとすぐに修正しており、現在は安定しています。

当時使用していた Jekyll (Chirpy Theme) のバージョンは v5.x で、大きな問題はなく、必要な機能も揃っていました(例:固定表示、カテゴリー、タグ、カバー画像、コメントなど)。ただし、画面をスクロールするときにスクロールできなくなることがよくありましたが、数回スクロールすると正常に戻るという操作性の欠点がありました。v6.x にアップグレードを試みましたが問題は解消せず、公式に報告しても返答はありませんでした。さらに、バージョンアップに伴う競合も増えたため、最終的にアップグレードを諦めました。

最近、Jekyll (Chirpy Theme) の問題を解決し、バージョンアップを決意しつつ、ついでに高速デプロイツール ZMediumToJekyll を再最適化しました。

新着!medium-to-jekyll-starter 🎉🎉

medium-to-jekyll-starter.github.io

私は Jekyll (Chirpy Theme) の最新版 v7.x に、私の ZMediumToMarkdown Medium記事ダウンロード変換ツールを組み合わせて、新しい — medium-to-jekyll-starter.github.io Githubテンプレートリポジトリを作成しました。

皆さんはこのテンプレートリポジトリを使って、自分のMediumミラーコンテンツのバックアップサイトを素早く設定・構築できます。一度設定すれば永久に自動バックアップが続き、Github Pages上で完全無料でデプロイされます。

手順ごとの設定方法は、こちらの記事をご参照ください: https://zhgchg.li/posts/medium-to-jekyll/

成果

<https://zhgchg.li/>{:target="_blank"}

https://zhgchg.li/

上記のすべての記事は、私の Medium からすべての内容を自動的にダウンロードし、Markdown形式に変換して再アップロードしたものです。

適当な記事の変換結果を比較例として添付:

Medium 上の元の内容 / 変換後の個人サイトでの結果

アップグレード後、スクロールが止まる問題は再発しませんでした。このアップグレードを機に、カスタマイズされた動的コンテンツ(Mediumのフォロワー数表示)も追加しました。

いくつかの技術記録

Jekyll (Chirpy Theme) を Github Pages にデプロイする設定方法は、主に公式の Start Repo を直接参照します:

先月もこのプロジェクトの方法を参考にして、新しいオープンソースプロジェクト — Linkyee オープンソース版の Link Tree 個人リンクページを作成しました。

<https://link.zhgchg.li/>{:target="_blank"}

https://link.zhgchg.li/

Jekyll カスタマイズ方法 (1) — HTMLのオーバーライド

Jekyll は強力な Ruby 製の静的サイトジェネレーターです。 Jekyll (Chirpy Theme) は Jekyll をベースにしたテーマの一つで、他のテーマと比べても Chirpy Theme が最も品質が高く、操作性に優れ、機能も充実しています。

Jekyll のページは継承性があり、./_layoutsJekyll と同じページファイル名 を追加すると、サイト生成時にエンジンが自作のページ内容で元の内容を置き換えます。

例えば、各記事ページの末尾に一行の文章を追加したい場合、元の投稿ページファイル(post.html)をコピーして、./_layouts ディレクトリに置きます:

編集者で post.html を開き、該当箇所にテキストやカスタマイズを追加してからサイトを再デプロイすると、カスタマイズ結果が反映されます。

./_include ディレクトリを作成し、共有したいページのコンテンツファイルを入れることもできます:

そして post.html では、直接 {% include buymeacoffee.html %} を使って先ほどのファイルの HTML 内容を再利用できます。

HTML Layout ファイルを上書きする利点は、100% カスタマイズ可能で、ページの内容やレイアウトを自由に調整できることです。欠点は、今回のアップグレード時に競合や予期しない結果が発生しやすく、カスタマイズ内容を再度確認する必要がある点です。

Jekyll カスタマイズ方法 (2) — プラグイン

第二の方法は、Plugin の中の Hook を使い、Jekyll が静的コンテンツを生成する段階で自分のカスタマイズした内容を注入する方法です。

[Built-in Hook Owners and Events](https://jekyllrb.com/docs/plugins/hooks/#built-in-hook-owners-and-events){:target="_blank"}

Builtin Hookの所有者とイベント

Hook 事件 はたくさんありますが、ここでは私が使った site:pre_renderpost:pre_render だけを載せます。

追加方法もとても簡単で、./_plugins に Ruby ファイルを追加するだけです。

posts-lastmod-hook.rb は元々あるプラグインです

posts-lastmod-hook.rb は元々あるプラグインです

いくつかの「擬似」動的コンテンツ機能を追加したいです。最初は、プロフィールの下に Medium のフォロワー数を表示し、ページのフッターにページ内容の最終更新日時を表示することです。

./_pluginszhgchgli-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"
# _config.ymlで定義して、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}+ Mediumのフォロワー</a>
  HTML

  site.config['tagline'] = "#{followMe}";
  site.config['tagline'] += tagline;

  meta_data = site.data.dig('locales', 'en', 'meta');
  # 英語のみの実装ですが、他の言語にも対応可能です。

  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/>最終更新: #{formatted_time} +08:00"
  end
end
  • 原理は、サイトのレンダー前にフックを登録し、config の tagline 個人プロフィール下の紹介欄に Medium のフォロワー数表示用の HTML を追加することです。

  • Medium のフォロワー数は、毎回実行時に最新の数字を取得するためにスクレイピングされます。

  • ページ下部の最終更新日時のロジックもほぼ同じで、サイト生成時に locales->en->meta に最終更新日時の文字列を追加しています。

  • 補足として、Hookが記事生成前の場合はMarkdownを取得でき、記事生成後の場合は生成されたHTMLを取得できます。

保存後は、まずローカルで bundle exec jekyll s を実行して結果をテストできます:

ブラウザで 127.0.0.1:4000 を開いて結果を確認してください。

最後に Github Pages リポジトリの Actions にスケジュールを追加して定期的にサイトを自動再生成することで、完了しました:

Jekyll (Chirpy Theme) リポジトリの 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: サイトアーティファクトをアップロード
        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: 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) のデプロイには、サイトの内容が正しいかどうかをテストするステップがあります。例えば、リンクが正常か、HTMLタグに欠落がないかなどを検証します。

1
2
3
4
5
6
7
8
9
# build:
# ...
      - name: サイトのテスト
        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とalt属性のないHTMLタグのチェックを無視しました。これら二つを無視して検査を通過させました(内容をすぐに変更できないため)

htmlproofer の CLI コマンドは公式ドキュメントに記載がなく、長時間調べた末にある Issue の コメント でルールを見つけました:

<https://github.com/gjtorikian/html-proofer/issues/727#issuecomment-1334430268>{:target="_blank"}

https://github.com/gjtorikian/html-proofer/issues/727#issuecomment-1334430268

その他の記事補足

ご質問やご意見がありましたら、こちらからご連絡ください

PostZMediumToMarkdown によって Medium から変換されました。


🍺 Buy me a beer on PayPal

👉👉👉 Follow Me On Medium! (1,053+ Followers) 👈👈👈

本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

Improve this page on Github.

本記事は著者により CC BY 4.0 に基づき公開されています。