
アプリの紹介ページやブログ記事を 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 ファイル以外の変更ではワークフローが起動しません。
ワークフローのステップ概要
- Checkout — リポジトリをクローン(
fetch-depth: 2で差分検出用に2コミット取得) - Set up Python — Python 3.12 セットアップ
- Install dependencies —
requests,markdown,PyYAMLをインストール - Detect changed files —
git diffで変更された Markdown ファイルを特定 - Post to WordPress —
autoPost.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_URL、WP_USER、WP_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@v4 の token: を明示指定していなかった。
解決: 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.py(http://localhost:8765) |
Markdown を書いて git push するだけで記事が反映される体験は快適です。次は画像の alt テキスト自動生成や、投稿後に Slack 通知を飛ばす仕組みも試してみたいと思っています。
お願い
本記事の情報は参考目的で掲載しており、正確性・完全性を保証するものではありません。誤記・不正確な情報がございましたら、下のコメント欄よりご指摘いただければ、確認のうえ修正いたします。ご協力をお願いいたします。
コメントを残す