GitHub Actions × WordPress REST API で Markdown 自動投稿パイプラインを構築した

,

git-wordpress

アプリの紹介ページやブログ記事を Markdown で書いて git push するだけで WordPress に自動反映される仕組みを整えました。構築中にいくつかハマりどころがあったので、構成と解決策をまとめます。


システム構成

ローカル Markdown 編集
       ↓  git push (main ブランチ)
GitHub リポジトリ
       ↓  GitHub Actions トリガー
auto_post.py (Python スクリプト)
       ↓  WordPress REST API
WordPress サイト(投稿 / 固定ページ)

フォルダ構成

4-post/
  fix/         ← WordPress「固定ページ」(about, features など)
  blog/        ← WordPress「投稿(ブログ)」
  org/         ← 画像・元素材(自動投稿対象外)
autoPost.py   ← 投稿スクリプト本体

Markdown フロントマター仕様

ファイル先頭に YAML フロントマターを記述します。

---
title: 記事タイトル
slug: article-slug        # WordPress の パーマリンク slug
status: draft             # draft または publish
categories: [カテゴリA]   # blog のみ有効
tags: [タグ1, タグ2]      # blog のみ有効
---

slug をキーとして既存の投稿を upsert(存在すれば更新、なければ新規作成) します。


GitHub Actions ワークフロー

.github/workflows/auto_post.yml の主要な設定:

on:
  push:
    branches: [main]
    paths:
      - "4-post/fix/**/*.md"
      - "4-post/blog/**/*.md"
  workflow_dispatch:        # GitHub の Actions タブから手動実行も可能

paths フィルタにより、Markdown ファイル以外の変更ではワークフローが起動しません。

ワークフローのステップ概要

  1. Checkout — リポジトリをクローン(fetch-depth: 2 で差分検出用に2コミット取得)
  2. Set up Python — Python 3.12 セットアップ
  3. Install dependenciesrequests, markdown, PyYAML をインストール
  4. Detect changed filesgit diff で変更された Markdown ファイルを特定
  5. Post to WordPressautoPost.py を実行して各ファイルを投稿

差分検出の仕組み

# push 時: 前のコミットとの差分から対象ファイルを抽出
git diff --name-only HEAD~1 HEAD -- "4-post/fix/*.md" "4-post/blog/*.md" || true

# 手動実行時: 全 Markdown ファイルを対象
find 4-post/fix 4-post/blog -name "*.md" 2>/dev/null || true

|| true は、対象ファイルがゼロ件のときに grep/find が終了コード 1 を返してワークフローが失敗するのを防ぐためです。


autoPost.py の処理フロー

def post_markdown(md_file_path):
    1. フロントマター(YAML)を解析
    2. 本文中の画像パスを WordPress メディアライブラリへアップロード
    3. 画像の相対パスを WordPress URL に置換
    4. Markdown → HTML に変換
    5. REST API で投稿を upsert(slug で既存記事を検索)

認証

WordPress の アプリケーションパスワード を使用します。GitHub Secrets に登録した値を環境変数から取得します。

WP_BASE_URL = os.environ["WP_BASE_URL"]   # 例: https://example.com
WP_USER     = os.environ["WP_USER"]       # WordPress ユーザー名
WP_TOKEN    = os.environ["WP_TOKEN"]      # アプリケーションパスワード

GitHub Secrets(Settings → Secrets and variables → Actions)に WP_BASE_URLWP_USERWP_TOKEN の3つを登録しておきます。


画像アップロードの実装

Markdown 中の相対パス画像を WordPress メディアライブラリにアップロードし、URL に置換します。

# 画像アップロード時の Content-Disposition ヘッダー
safe_name = _safe_filename(abs_img)   # 日本語 → ASCII 変換
headers = {
    "Content-Disposition": f'attachment; filename="{safe_name}"',
    "Content-Type": _mime(abs_img),
}
res = requests.post(f"{WP_BASE_URL}/wp-json/wp/v2/media",
                    auth=(WP_USER, WP_TOKEN),
                    headers=headers,
                    data=abs_img.read_bytes())

ハマりポイント:RFC 5987 は WordPress が非対応

日本語ファイル名を filename*=UTF-8''%E8%B5... の形式(RFC 5987)で送ると WordPress が HTTP 400 を返します。

{"code":"rest_upload_invalid_disposition",
 "message":"Content-Disposition は attachment; filename=\"image.png\" の形式でなければなりません"}

解決策: 非 ASCII 文字を除いた ASCII 安全なファイル名を生成します。

def _safe_filename(path: Path) -> str:
    stem = re.sub(r'[^\x20-\x7E]', '', path.stem).strip()
    stem = re.sub(r'[^\w\-]', '-', stem).strip('-')
    if not stem:
        # 日本語のみのファイル名は MD5 ハッシュで代替
        stem = hashlib.md5(path.name.encode()).hexdigest()[:12]
    return f"{stem}{path.suffix.lower()}"
元のファイル名 変換後
起動画面.png a3f8c2b19d40.png (MD5ハッシュ)
feature-csv入力.png feature-csv.png (ASCII部分を保持)
about-screen.png about-screen.png (変換なし)

ローカルプレビュー

preview_post.py を使うと、WordPress に投稿する前にブラウザで確認できます。

python3 preview_post.py
# → http://localhost:8765 でプレビューサーバー起動

画像は Base64 エンコードしてインライン埋め込みするため、ローカルの相対パス画像もそのまま表示されます。


構築中に遭遇したトラブルと解決策

1. ワークフローが一切実行されない

原因: push トリガーの対象ブランチが main なのに、作業ブランチが別の名前だった。
解決: git checkout main && git merge <作業ブランチ> && git push origin main

2. フロントマターライブラリが Python 3.9 で動かない

原因: python-frontmatter が内部で typing.TypeGuard(Python 3.10+)を使用。
解決: PyYAML + 正規表現による自前パースに切り替え。GitHub Actions 側は Python 3.12 を使用。

3. find コマンドで exit code 1 エラー

原因: 対象ディレクトリが空のとき find が exit code 1 を返し、ワークフローが失敗。
解決: コマンド末尾に || true を追加。

4. Checkout で HTTPS 認証エラー

原因: actions/checkout@v4token: を明示指定していなかった。
解決: token: ${{ secrets.GITHUB_TOKEN }} を追加(Actions に標準で用意されているため追加設定不要)。

5. アプリケーションパスワードを誤って git にコミット

原因: .env ファイルを .gitignore に追加する前にコミットした。
解決: .gitignore.env を追加。git 履歴にトークンが残るため、WordPress 側でそのアプリケーションパスワードを失効させ、新しいパスワードを発行して GitHub Secrets を更新。


セキュリティ上の注意点

  • 認証情報は必ず GitHub Secrets に格納する。ソースコードや .env ファイルには直書きしない。
  • .env は必ず .gitignore に追加する。.env.example に項目名だけを記載してリポジトリに含めると他のメンバーにも共有しやすい。
  • アプリケーションパスワードが漏洩した場合は、WordPress 管理画面(ユーザー → プロフィール)から即座に失効させる。

まとめ

項目 内容
トリガー main ブランチへの 4-post/**/*.md push、または手動実行
認証 WordPress アプリケーションパスワード(GitHub Secrets 管理)
画像 メディアライブラリへ自動アップロード(ASCII 安全なファイル名)
upsert slug キーで既存記事を検索し、存在すれば更新・なければ新規作成
ローカル確認 preview_post.pyhttp://localhost:8765

Markdown を書いて git push するだけで記事が反映される体験は快適です。次は画像の alt テキスト自動生成や、投稿後に Slack 通知を飛ばす仕組みも試してみたいと思っています。


お願い
本記事の情報は参考目的で掲載しており、正確性・完全性を保証するものではありません。誤記・不正確な情報がございましたら、下のコメント欄よりご指摘いただければ、確認のうえ修正いたします。ご協力をお願いいたします。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

PAGE TOP