ZensicalでGitHub Pages公開する
zenn.devとcosense(旧scrapbox)を使用しているのですが、いざというときのためにbackupが欲しいなと唐突に思いました。
markdownファイルをいい感じにGitHub Pagesで公開できるようにしたい、と思って調べていたところ、Zensicalというツールを見つけたので、使ってみることにします。
Zensical¶
Material for MkDocsの開発チームが作ったRust製の静的サイトジェネレータです。
出来上がったもの¶
zenn-content¶
あとは、zensical.tomlのproject情報を書き、project.doc_dirを"articles"に変更しました。
articles/index.md に適当な見出しページを作って、uv run zensical serve すると、localhost:8000 でプレビューできます。

GitHub Pagesで公開する方法もZensicalのドキュメントにサンプルがあります。
https://zensical.org/docs/publish-your-site/
これで、mainブランチにpushしたら公開できる仕組みの出来上がりです。
cosense-content¶
cosenseの方は一旦markdownに変換する必要があります。
Pythonで変換用のコードを公開している方がいらしたので、ありがたく使わせていただきました。
https://github.com/matsushinDB11/Scrapbox_to_md/blob/master/Scrapbox_to_Notion.py
indentが2だとzensicalがレベル判定してくれないので4にしたり、全角スペースもindentにしたりといった軽い修正を加えていますが、ほとんどそのまま利用でき、大変助かりました。
定期的にcosenseの記事をダウンロードしたいので、日付指定でcosenseの内部APIを呼んでダウンロードします。
https://scrapbox.io/help-jp/API
あくまで内部APIです。APIは予告なく変更を行います。
とのことなので、変更されたら追随しないとですね。 シェルスクリプトをざっと書いて、Codexに相談してコマンドラインオプション周りの機能を追加してもらいました。
#!/usr/bin/env bash
set -euo pipefail
set -xv
PROJECT="watarukura"
usage() {
echo "Usage: $0 [-d YYYY-MM-DD|--since YYYY-MM-DD] [--skip-download]" >&2
exit 2
}
since_date=""
skip_download=0
while [[ $# -gt 0 ]]; do
case "$1" in
-d|--since)
[[ $# -ge 2 ]] || usage
since_date="$2"
shift 2
;;
--since=*)
since_date="${1#*=}"
shift
;;
-h|--help)
usage
;;
--skip-download)
skip_download=1
shift
;;
*)
echo "Unknown option: $1" >&2
usage
;;
esac
done
# 日付指定がある場合
since_epoch=""
if [[ -n "$since_date" ]]; then
if date -j -f "%Y-%m-%d" "$since_date" "+%s" >/dev/null 2>&1; then
since_epoch=$(date -j -f "%Y-%m-%d" "$since_date" "+%s")
else
since_epoch=$(date -d "$since_date" "+%s")
fi
fi
title_filter='.pages[].title'
jq_args=()
if [[ -n "$since_epoch" ]]; then
title_filter='.pages[] | select(.updated >= $since_epoch) | .title'
jq_args=(--argjson since_epoch "$since_epoch")
fi
# cosenseから記事一覧をダウンロードする
page=1
if [[ "$skip_download" -eq 0 ]]; then
curl -sL "https://scrapbox.io/api/pages/$PROJECT?sort=updated" >"articles$page"
count=$(jq -r .count "articles$page")
limit=$(jq -r .limit "articles$page")
if [[ "$count" == "null" || "$limit" == "null" ]]; then
echo "Failed to read page metadata from scrapbox API." >&2
exit 1
fi
while ((count > limit)); do
count=$((count - limit))
skip=$((page * limit))
page=$((page + 1))
curl -sL "https://scrapbox.io/api/pages/$PROJECT?sort=updated&skip=$skip" >"articles$page"
done
fi
# cosenseから記事をダウンロードする
if [[ "$skip_download" -eq 0 ]]; then
mkdir -p scrapbox
cat articles* |
jq -r "${jq_args[@]}" "$title_filter" |
sed -e 's;/;%2F;g' -e 's/ /_/g' |
while read -r title; do
curl -sL "https://scrapbox.io/api/pages/$PROJECT/$title/text" >"scrapbox/$title".sb
done
fi
# scrapbox形式からmarkdown形式に変換する
mkdir -p markdown
cat articles* |
jq -r "${jq_args[@]}" "$title_filter" |
sed -e 's;/;%2F;g' -e 's/ /_/g' |
while read -r title; do
uv run sb2md.py "scrapbox/$title".sb >"markdown/$title".md
done
で、このスクリプトを週次で実行して、Pull Requestを作るGitHub Actionsのworkflowを作ります。
週1回、直近1週間の更新分を取ってきて、Pull Requestを作ります。
マージしたら公開される、という仕組みです。
name: Weekly Cosense Update PR
on:
schedule:
- cron: "0 0 * * 2"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.13"
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: Fetch updated Cosense pages
env:
TZ: Asia/Tokyo
run: |
since_date=$(date -d "7 days ago" +%Y-%m-%d)
bash scripts/download_cosense.bash --since "$since_date"
- name: Create pull request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
commit-message: "Update Cosense content (weekly)"
title: "Update Cosense content (weekly)"
body: |
Automated weekly update for Cosense content.
branch: "automation/weekly-cosense-update"
delete-branch: true
まとめ¶
仕組みとしては難しいものではないのですが、自分のテックブログを持つことの安心感はありますね。
デザインがデフォルトのままだったり、オンラインドキュメントっぽいつくりなのでブログっぽくしたい、などやりたいことはあるので盆栽していこうと思います。