Claude Code × WP REST APIで下書き自動投稿 – NPC
WordPress REST APIとClaude Codeでブログ下書きを毎朝自動生成する仕組み
ブログを継続的に更新したい。でも、毎日ネタを考えて書いて投稿して……となると、どうしても手が止まる。特にフリーランスだと、案件対応が優先で自分のブログは後回しになりがちだと思う。
自分も同じ課題を抱えていて、いろいろ試した結果、「Claude Code + WordPress REST API + launchdで毎朝Mac起動時にブログ下書きを自動生成する」という仕組みに落ち着いた。
この記事では、実際に稼働しているスクリプトの構成と考え方を共有する。WordPress開発者やAIを使った効率化に興味がある人の参考になれば。
全体の構成とフロー
仕組みの全体像はシンプルで、以下の流れで動いている。
処理の流れを図にするとこうなる。
- Mac起動(またはログイン)→
launchdがシェルスクリプトを実行 - ネタリスト(テキストファイル)から未使用のテーマを1つ取得
- Claude Code のヘッドレスモード(
claude -p)で記事HTMLとメタ情報を生成 - WordPress REST API で下書きとして投稿(カテゴリ・Yoast SEOメタ付き)
- X投稿用ドラフトをローカルに保存
- 自分が確認 → 修正して公開 → X投稿
完全自動で公開まではしない。あくまで「下書きまで自動、公開は人間が判断」という設計にしている。理由は単純で、AIが生成した文章をそのまま公開するのは品質面でリスクがあるから。
ファイル構成
プロジェクト内のファイル構成はこんな感じ。
~/npc-team/scripts/
├── run-blog.sh # メインスクリプト
├── blog-topics.txt # ネタリスト(パイプ区切り)
├── drafts/ # X投稿文・記事HTMLバックアップ
└── logs/ # 実行ログ + 実行済みフラグ
~/npc-team/launchd/
└── com.npc.blog-draft.plist # launchd設定
それぞれのファイルが担う役割を整理するとこうなる。
- run-blog.sh(メインスクリプト)
- ネタ取得・Claude Code呼び出し・WP投稿を一括処理
- blog-topics.txt
- 記事テーマをパイプ区切りで管理。週1で補充
- drafts/
- X投稿文・記事HTMLのローカルバックアップ
- logs/
- 実行ログ・当日実行済みフラグ(重複実行防止)
- launchd/com.npc.blog-draft.plist
- Mac起動時の自動実行を制御するlaunchd設定
Claude Codeをヘッドレスで使う
Claude Codeには2つの使い方がある。
通常モード(対話モード)
シェフと一緒にキッチンに立って料理するイメージ。「もう少し塩足す?」「こっちの具材に変えよう」とやり取りしながら完成させる。
ヘッドレスモード
メニューを書いて厨房に渡すイメージ。シェフが一人で作って、完成品がカウンターに出てくる。こちらはキッチンに入らない。
※「ヘッドレス」=画面(head)なしで動かすこと。人間が操作する画面を使わず、プログラムから直接命令を出す方式です。
具体的には、Claude Code の -p オプション(プロンプトモード)を使う。ターミナルからワンショットでプロンプトを投げて、結果をそのまま標準出力で受け取れる。つまり、シェルスクリプトの中からClaude Codeを呼び出して、その出力を変数に格納できる。
※「ワンショット」=一回きりの注文のこと。追加オーダーや味見のやり取りなしで、最初の注文だけでシェフに作ってもらいます。
CLAUDE_OUTPUT=$(claude -p "$PROMPT" --max-turns 3)
--max-turns 3 はエージェントが内部で何回ツールを使えるかの上限。記事生成のような単発タスクなら3回あれば十分。
プロンプト設計のポイント
記事生成のプロンプトでは、出力を「記事本文(HTML)」と「メタ情報(JSON)」の2ブロックに分け、=== で区切る形にしている。こうすることで、シェルスクリプト側で awk を使って簡単に分割できる。
# === で分割
ARTICLE_HTML=$(echo "$CLAUDE_OUTPUT" | awk '/^===/{found++; next} found==0{print}')
META_JSON=$(echo "$CLAUDE_OUTPUT" | awk '/^===/{found++; next} found>=1{print}')
ここで重要なのが、AIの出力は毎回フォーマットが微妙にブレるということ。「記事が完成しました」みたいな前置きが入ったり、コードフェンス(```html)で囲まれたりする。
最初は「必要な行だけ残す」方式でパースしていたが、それだと記事本文ごと消えてWordPressに空の下書きが投稿される事故が起きた。今は「不要な行を明示的に削除する」方式に変えている。
# 不要な行だけ削除する(本文は残す)
ARTICLE_HTML=$(echo "$ARTICLE_HTML" | sed '/^```/d' | sed '/^記事が完成しました/d')
この考え方は地味だけど大事で、AI出力のパースでは「寛容に」書くのが鉄則だと思う。
WordPress REST APIで下書き投稿する
WordPress REST API(WordPressに標準搭載されている、外部からデータを読み書きできるインターフェース)を使って、curl コマンドで下書き投稿を行う。
認証方式
認証にはWordPressのアプリケーションパスワード機能を使っている。WordPress管理画面の「ユーザー → プロフィール → アプリケーションパスワード」から生成できる。
認証情報は ~/.env に保存して、スクリプト側で読み込む形式にしている。
# ~/.env
WP_API_URL=https://example.com/wp-json/wp/v2
WP_USER=your-username
WP_APP_PASSWORD=xxxx xxxx xxxx xxxx
投稿リクエスト
jq を使って安全にJSONペイロードを構築し、curl で投稿する。
WP_PAYLOAD=$(jq -n \
--arg title "$TITLE" \
--arg content "$ARTICLE_HTML" \
--arg slug "$SLUG" \
--arg status "draft" \
--argjson categories "$CAT_JSON" \
'{
title: $title,
content: $content,
slug: $slug,
status: $status,
categories: $categories
}')
curl -s -X POST "${WP_API_URL}/blog" \
-H "Content-Type: application/json" \
-u "${WP_USER}:${WP_APP_PASSWORD}" \
-d "$WP_PAYLOAD"
ここで jq -n --arg を使っているのがポイント。記事本文にはHTMLタグや改行、ダブルクォートが含まれるため、手動でJSON文字列を組み立てるとエスケープ漏れで投稿が失敗する。jq に任せれば安全にエスケープしてくれる。
Yoast SEOメタの設定
ただし、WordPress REST APIは標準だとYoast SEOのフィールド(フォーカスキーフレーズやメタディスクリプション)を受け付けない。これを解決するために、mu-plugin(Must-Use Plugin、WordPress起動時に必ず読み込まれるプラグイン)を1つ追加している。
// wp-content/mu-plugins/npc-rest-yoast-meta.php
// Yoast SEO のメタフィールドをREST APIに公開する
これにより、投稿リクエストの meta フィールドで _yoast_wpseo_focuskw、_yoast_wpseo_title、_yoast_wpseo_metadesc を設定できるようになる。
カテゴリの自動マッピング
ネタリストにはカテゴリ名を日本語で書いているが、WordPress REST APIにはカテゴリIDで渡す必要がある。macOS標準の bash はバージョン3.2で連想配列(declare -A)が使えないため、case 文で変換している。
get_category_id() {
case "$1" in
"AI・ツール活用") echo 12 ;;
"WordPress・Web制作") echo 9 ;;
# ...
*) echo "" ;;
esac
}
Homebrew版のbash 5を使えば連想配列も使えるが、環境依存を減らすためにあえて標準bashで動く書き方にしている。
launchdで毎朝自動実行する
macOSには launchd という仕組みがあり、ログイン時やスケジュールでスクリプトを自動実行できる。cronのmacOS版と思ってもらえればいい。
~/Library/LaunchAgents/ にplistファイルを配置して launchctl load すれば有効になる。自分の場合はログイン時に実行する設定にしていて、Macを開けば自動で記事生成が走る。
ただし、1日に何回もMacを再起動することもあるので、「当日すでに実行済みなら何もしない」というガードを入れている。
DONE_FLAG="${LOG_DIR}/.done-${TODAY}"
if [[ -f "$DONE_FLAG" ]]; then
echo "[INFO] 本日はすでに実行済みです。スキップします。"
exit 0
fi
ログディレクトリに日付付きのフラグファイルを作るだけのシンプルな仕組みだが、十分に機能している。
運用してみての所感
この仕組みを稼働させてから、ブログ更新のハードルが大きく下がった。朝Macを開くと下書きがもう入っているので、「ゼロから書く」という一番重い作業が消える。
ただ、完全放置でいいかというとそうでもない。実際に運用してみて気づいた点をいくつか。
長く続けるために意識しておきたいポイントをまとめる。
- ネタリストの管理が地味に大事: リストが尽きると何も生成されない。週1で10本くらいまとめて追加するようにしている
- 記事の品質はプロンプト次第: トーンや構成のルールを細かく指定しないと、一般的すぎる文章が出てくる。プロンプトは何回も調整した
- 確認・修正は必ずやる: AIが生成した記事をそのまま公開したことは一度もない。事実確認、表現の調整、自分の経験の追記は毎回やっている
- アイキャッチ画像は別対応: Claude Codeでは画像生成できないので、別の仕組み(cowork)でアイキャッチを自動生成して設定している
仕組みとしては「AIがドラフトを書き、人間がレビューして公開する」という分業。これがフリーランスの個人ブログ運営としては、今のところ一番バランスがいいと感じている。
まとめ
Claude Code × WordPress REST API × launchdで、毎朝自動でブログ下書きが生成される仕組みを作った。
- Claude Code の
-pオプションでヘッドレス実行し、記事HTMLとメタ情報を生成 - WordPress REST API + アプリケーションパスワードで下書き投稿(Yoast SEOメタ含む)
- launchdでMac起動時に自動実行、1日1回制御付き
- 完全自動公開はせず、必ず人間がレビューしてから公開
ブログ更新を継続したいけど毎日書くのはしんどい、という人にはこの「半自動」アプローチがおすすめ。技術的にはシェルスクリプトとcurlが分かれば実装できるレベルなので、興味があればぜひ試してみてほしい。
※この記事はNPCの中の人の実務経験をもとに書いています。