「AIに記事を書かせるところまではできた。でも、そこからWordPressにコピペするのが結局めんどくさい」
僕はここで止まっていました。
生成そのものより、書いたものを実際にサイトへ運ぶ作業のほうが時間を食う。見出しの装飾が崩れる、表がプレーンテキストになる、毎回同じ手順を繰り返す。この往復が地味に効いてきます。
この記事では、Claude CodeからWordPressへ直接投稿する仕組みを作った手順を、実際に動かしたコードごと書きます。僕はエンジニアではありません。 プログラミングを仕事にしたことは一度もなく、この仕組みもClaude Codeに書かせながら組み上げたものです。
所要時間はだいたい30分。無料でできます。
この記事でできるようになること
- ローカルのMarkdownファイルを、コマンド1つでWordPressの下書きにする
- 見出し・表・リスト・引用・リンクが崩れずにブロックエディタへ入る
- 記事の更新・公開・画像アップロードもコマンドで完結する
つまり、ブラウザでWordPressの管理画面を開かずに記事を投稿できる状態になります。
python3 wp_client.py draft article.md
→ 下書き作成: id=112
必要なもの
| 項目 | 補足 |
|---|---|
| WordPressサイト | バージョン5.6以上。レンタルサーバーの標準的なもので大丈夫です |
| 管理者アカウント | アプリケーションパスワードを発行するため |
| HTTPS | 常時SSL化されていること。今のサーバーならほぼ標準です |
| Python 3 | Macなら最初から入っています |
| Claude Code | コードを書かせる用。手打ちしたい人は不要です |
WordPress.comの無料プランではREST APIの書き込みが制限されるので、自分でサーバーを借りているWordPress(エックスサーバー、ConoHa、ロリポップなど)が前提になります。
まだWordPressを持っていない場合は、先にこちらを読んでください。自動投稿する前提だと、サーバー選びで確認すべき条件が変わります。
→ エックスサーバーでWordPressを始める手順|AIで自動投稿する前提のサーバー選び
Step 1|アプリケーションパスワードを発行する
ここが最初のポイントです。通常のログインパスワードは使いません。
アプリケーションパスワードは、外部プログラム専用の合鍵のようなものです。普段のログインとは別に発行され、いらなくなったらそれだけを無効化できます。 万一漏れても、本体のパスワードは影響を受けません。
- WordPress管理画面にログイン
- 左メニュー 「ユーザー」→「プロフィール」
- ページ最下部の 「アプリケーションパスワード」
- 名前欄に用途を入れる(例:
claude-code) - 「新しいアプリケーションパスワードを追加」
すると、こういう文字列が1回だけ表示されます。
abcd EFGH 1234 ijkl MNOP 5678
この画面を閉じると二度と表示されません。 必ずコピーしておいてください。
スペースは入ったままで問題ありません。プログラム側で処理されます。
項目が見つからない場合
アプリケーションパスワードはHTTPS接続でないと表示されません。サイトが
http://になっていないか確認してください。セキュリティ系プラグインが機能ごと無効化していることもあります。
Step 2|REST APIが使えるか確認する
ブラウザで次のURLを開いてください。example.com は自分のドメインに置き換えます。
https://example.com/wp-json/
JSONの塊が表示されれば有効です。真っ白だったり404が出る場合は、REST APIがプラグインなどで無効化されています。
この中に application-passwords という記述があれば、Step 1で作った鍵が使える状態です。
Step 3|接続テストをする
いきなり投稿せず、まず読み取りだけを試します。ここで失敗しておくほうが安全です。
ターミナルで次を実行します。ユーザー名は表示名ではなくログインIDなので注意してください。
curl -u "ユーザー名:abcd EFGH 1234 ijkl MNOP 5678" \
"https://example.com/wp-json/wp/v2/users/me?context=edit"
成功すると、こういう応答が返ります。
{"id":1,"username":"your_id","roles":["administrator"], ... }
roles に administrator が入っていれば準備完了です。
401 が返る場合はユーザー名かパスワードの間違い、403 の場合はセキュリティプラグインがREST APIを塞いでいる可能性が高いです。
Step 4|投稿スクリプトを用意する
ここからClaude Codeの出番です。僕は次のように頼みました。
WordPressのREST APIに接続して、Markdownファイルを下書き投稿するPythonスクリプトを書いて。認証はアプリケーションパスワードで、Markdownはブロックエディタの形式に変換して。追加ライブラリは使わないで。
「追加ライブラリを使わないで」と指定したのがポイントでした。最初は外部ライブラリを使う版が出てきたのですが、インストールでつまずくと初心者はそこで止まります。Pythonに最初から入っている機能だけで書かせたほうが、動き出すまでが速いです。
出てきたコードの核心部分がこれです。
import base64, json, urllib.request
WP_URL = "https://example.com"
WP_USER = "your_id"
WP_PASS = "abcd EFGH 1234 ijkl MNOP 5678"
def post_draft(title, content_html):
url = f"{WP_URL}/wp-json/wp/v2/posts"
token = base64.b64encode(f"{WP_USER}:{WP_PASS}".encode()).decode()
payload = json.dumps({
"title": title,
"content": content_html,
"status": "draft", # 必ず下書き
}).encode()
req = urllib.request.Request(url, data=payload, method="POST", headers={
"Authorization": "Basic " + token,
"Content-Type": "application/json",
})
with urllib.request.urlopen(req) as res:
result = json.load(res)
print(f"下書き作成: id={result['id']}")
return result
やっていることは単純で、ユーザー名とパスワードをbase64で繋いでヘッダーに入れ、JSONをPOSTしているだけです。
"status": "draft" は固定にしておくことを強くおすすめします。 ここを publish にできる設計にすると、テスト中に書きかけの記事が公開されます。僕は公開を別コマンドに分けました。
認証情報はファイルに直接書かない
上のコードはパスワードを直書きしていますが、実際の運用では別ファイルに逃がします。
.env というファイルを作って、
WP_URL=https://example.com
WP_USER=your_id
WP_APP_PASSWORD=abcd EFGH 1234 ijkl MNOP 5678
.gitignore に .env の1行を追加します。これをやらずにGitHubへ上げると、サイトの鍵を公開することになります。
Step 5|MarkdownをブロックエディタのHTMLに変換する
ここが一番の山でした。
WordPressのブロックエディタ(Gutenberg)は、ただのHTMLではなく専用のコメントで囲まれた形式を使います。見出し1つでもこうなります。
<!-- wp:heading {"level":2} -->
<h2>見出しです</h2>
<!-- /wp:heading -->
このコメントを付けずに素のHTMLを投げると、投稿自体は成功します。ただしエディタで開いたときに「クラシック」ブロックという塊になり、あとから部分編集がしづらくなります。
変換部分もClaude Codeに書かせました。
import re
def md_to_blocks(md):
out = []
for line in md.split("\n"):
line = line.strip()
if not line:
continue
heading = re.match(r"(#{1,6})\s+(.*)", line)
if heading:
lv = len(heading.group(1))
out.append(f'<!-- wp:heading {{"level":{lv}}} -->\n'
f'<h{lv}>{heading.group(2)}</h{lv}>\n'
f'<!-- /wp:heading -->')
else:
out.append(f'<!-- wp:paragraph -->\n<p>{line}</p>\n'
f'<!-- /wp:paragraph -->')
return "\n\n".join(out)
これは見出しと段落だけの最小版です。実際にはここへリスト・表・引用・コードブロック・太字やリンクの処理を足していきます。
全部いっぺんに作らないのがコツでした。 最初は見出しと段落だけで動かし、投稿してエディタで確認し、崩れたところだけClaude Codeに直させる。この繰り返しのほうが、完璧な仕様を最初に伝えようとするより速く仕上がりました。
実際に詰まった3つのポイント
① ユーザー名は「表示名」ではない
管理画面に出ている表示名と、ログインIDが違うことがあります。僕はここで401を何度か出しました。
確認方法はこれです。
https://example.com/wp-json/wp/v2/users
表示される slug が、だいたいログインIDと一致します。
② パーマリンク設定はAPIから変更できない
意外な落とし穴でした。記事の作成・更新・削除、カテゴリの追加、キャッチフレーズの変更まではAPIでできます。でも「設定 → パーマリンク」の構造だけは、REST APIから触れません。
ここは管理画面で手動で設定する必要があります。自動投稿を始める前に済ませておくのが正解です。記事が増えてからURL構造を変えると、既存記事のURLが全部変わります。
おすすめは /%postname%/ です。日付が入らないので、記事を更新しても古く見えません。
③ 日本語のスラッグはURLが壊れる
WordPressは記事タイトルから自動でスラッグ(URL末尾)を作りますが、日本語タイトルだとそのまま日本語になります。すると実際のURLはこうなります。
https://example.com/%e6%96%b0nisa%e3%81%a7%e3%82%aa%e3%83%ab%e3%82%ab%e3%83%b3...
読めませんし、SNSで共有したときにリンクが途中で切れることがあります。
投稿時に slug を英数字で明示的に指定するのが確実です。
payload = json.dumps({
"title": "新NISAでオルカン1本を選んだ理由",
"slug": "nisa-all-country-reason", # 英数字で指定
"content": content_html,
"status": "draft",
})
これは記事だけでなくカテゴリでも同じです。日本語名のカテゴリを管理画面から作ると、スラッグも日本語になります。作成時に英数字を指定してください。
自動化できないこと(正直に書きます)
ここまで読むと全部自動化できそうに見えますが、そうではありません。実際に組んでみて分かった線引きです。
| できること | できないこと |
|---|---|
| 記事の作成・更新・公開 | パーマリンク構造の変更 |
| カテゴリ・タグの操作 | テーマ独自の設定画面(Cocoon設定など) |
| 画像アップロード・アイキャッチ設定 | Search Consoleの所有権確認 |
| キャッチフレーズなど基本設定 | プラグインの新規インストール |
| プラグインの有効化・停止 | 記事の内容が良いかどうかの判断 |
テーマ独自の設定は、WordPress標準の仕組みの外にあるため、APIからは基本的に触れません。SEO系プラグインのメタ設定なども同様です。
そして最後の行が一番大事だと思っています。投稿を自動化しても、何を書くかと、公開していいかの判断は自分でやることになります。 僕は下書きまでを自動化して、公開ボタンだけ自分で押す運用にしました。この線引きが、今のところ一番しっくりきています。
まとめ
やったことを整理します。
- アプリケーションパスワードを発行する(通常のパスワードは使わない)
/wp-json/でREST APIが生きているか確認する- 読み取りだけで接続テストする
- Claude Codeに投稿スクリプトを書かせる(追加ライブラリなしを指定する)
- Markdownをブロック形式に変換する(見出しと段落から始める)
- 下書き固定で運用し、公開は手動にする
エンジニアではない僕でも、30分ほどで動くところまで行けました。コードを自分で書ける必要はなくて、「何が欲しいか」を説明できれば形になります。
コピペの往復が消えると、記事を書くこと自体のハードルが目に見えて下がります。同じところで止まっている人の参考になれば嬉しいです。
この記事は、実際に自分のサイトで動かしている仕組みをもとに書いています。環境によって手順が異なる場合があるので、うまくいかない点があればコメントで教えてください。


コメント