PR
AIで記事作成を効率化したいなら → 高品質SEO記事生成AIツール【Value AI Writer byGMO】
WordPressの管理画面から、100件の記事のリンクを1つずつ直す——考えただけで気が遠くなりますよね。WordPressにはREST APIが用意されていて、Pythonから記事の取得・更新・下書き化まで自動で操作できます。この記事では、実際に使っているコードをもとに、つまずきやすいポイント込みで手順をまとめます。
この記事でできるようになること
- アプリケーションパスワードで認証して、PythonからWordPressを操作する
- 記事の一覧を取得し、特定の記事だけを更新する
- 複数記事をまとめて処理する(連続で叩かない書き方)
- 403エラーで止まらないための、実務的な対処
WordPressのバージョンやプラグイン構成によって挙動は変わります。手順を試すときは、まず1件だけで実行して結果を確認するところから始めてください。
準備1:アプリケーションパスワードを発行する
REST APIで書き込みを行うには認証が必要です。WordPress 5.6以降にはアプリケーションパスワードという仕組みがあり、管理画面から発行できます。
- 管理画面の「ユーザー」→「プロフィール」を開く
- ページ下部の「アプリケーションパスワード」欄に、名前(例:python-script)を入力して追加
- 表示された24文字のパスワードを控える(この画面を閉じると二度と表示されません)
ログインパスワードを直接スクリプトに書くのは絶対に避けてください。アプリケーションパスワードは用途ごとに発行して、不要になったらその1つだけ削除できるのが利点です。
準備2:認証ヘッダを作る
アプリケーションパスワードは、「ユーザー名:パスワード」をBase64でエンコードしてBasic認証のヘッダに載せるのが基本形です。
import base64
import requests
BASE = "https://example.com/wp-json/wp/v2"
USER = "your-username"
APP_PASSWORD = "xxxx xxxx xxxx xxxx xxxx xxxx" # 発行したアプリケーションパスワード
token = base64.b64encode(f"{USER}:{APP_PASSWORD}".encode()).decode()
headers = {
"Authorization": "Basic " + token,
"Content-Type": "application/json",
}
パスワードは環境変数や .env ファイルから読む形にしておくと、スクリプトを共有しても中身が漏れません。
ステップ1:記事の一覧を取得する
r = requests.get(
f"{BASE}/posts",
headers=headers,
params={"per_page": 5, "_fields": "id,title,link,status"},
timeout=30,
)
r.raise_for_status()
for p in r.json():
print(p["id"], p["status"], p["link"])
_fields で取得する項目を絞ると、レスポンスが軽くなって処理が速くなります。全記事を舐めるときは、レスポンスヘッダの X-WP-Total(総件数)と X-WP-TotalPages(総ページ数)を見てページを回すのが確実です。
ステップ2:1件だけ更新する(必ずここから)
更新は対象のIDに対してPOSTします。いきなり100件流すのは絶対にやめてください。まず1件で成功を確認します。
post_id = 123
payload = {
"title": "新しいタイトル",
"content": "本文をここに書きます。
",
"status": "draft", # ← 必ず明示する
}
r = requests.post(f"{BASE}/posts/{post_id}", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json()["link"])
status は必ず指定してください。省略すると、意図せず公開記事が下書きに戻ったり、その逆が起きたりします。実際にこれを忘れて、公開状態が予期せず変わったことがありました。
ステップ3:変更前にバックアップを取る
機械的に一括更新すると、戻すのが大変です。変更前の内容をファイルに保存してから書き換えるのが鉄則です。編集用の内容を取るには context=edit を付けます(認証が必要)。
import json
before = requests.get(
f"{BASE}/posts/{post_id}",
headers=headers,
params={"context": "edit"},
timeout=30,
).json()
with open(f"backup-post-{post_id}.json", "w", encoding="utf-8") as f:
json.dump(before, f, ensure_ascii=False, indent=2)
ステップ4:複数記事をまとめて処理する
ここからが本題です。ただし、連続でリクエストを投げるとサーバー側の防御機構に引っかかります。対策は2つで、①失敗したら待って再試行する、②成功しても少し間を空ける、です。
import time
def post_with_retry(path, payload, retries=4):
"""403/429/503 のときは待って再試行する"""
for i in range(retries + 1):
r = requests.post(f"{BASE}/{path}", headers=headers, json=payload, timeout=30)
if r.status_code == 200:
return r.json()
if r.status_code in (403, 429, 503) and i < retries:
wait = 15 * (i + 1)
print(f"{r.status_code}: {wait}秒待って再試行します ({i + 1}/{retries})")
time.sleep(wait)
continue
r.raise_for_status()
for target in targets: # targets = [{"id": ..., "payload": {...}}, ...]
result = post_with_retry(f"posts/{target['id']}", target["payload"])
print("OK", result["link"])
time.sleep(1.5) # 連続で叩きすぎない
つまずいたポイントと対処
実際に運用していて詰まったのは、次の4つです。コードを書く前に知っておくと時間を節約できます。
| 症状 | 原因 | 対処 |
|---|---|---|
| 同じ内容でも、403になったり通ったりする | WAF系のセキュリティプラグインが、大きなPOSTリクエストを断続的にブロックしている | 20秒〜数分待って再試行する。本文のサイズ依存ではないこともあるので、内容を疑って作り直す必要はない |
| DELETEが必ず403になる | セキュリティ設定でDELETEメソッドが拒否されている | 削除ではなく status を trash にしてゴミ箱へ移動する(実質は同じで、復元もできる) |
| 公開状態が意図せず変わる | POST時に status を省略した | 更新時は必ず status を明示する |
| 絞り込んだつもりが全件返る | パラメータ名の間違い、またはページングの未処理 | per_page と page、_fields の動きを1件ずつ確認する |
安全に運用するための5つのルール
- まず1件で試す:成功も失敗も、1件で確認してから件数を増やします
- 変更前にバックアップを取る:JSONで保存しておけば、失敗しても元に戻せます
- ドライランを作る:実際に書き換えず、対象と変更内容をログに出すだけのモードを先に動かします
- 必ず待ち時間を入れる:連続アクセスはサーバーにも負担です。1件ごとに1〜2秒空けます
- statusを明示する:下書きで作るのか公開するのか、必ず自分で指定します
まとめ
WordPressのREST APIは、「取得 → 1件テスト → バックアップ → 一括処理(待ち時間つき)」という順番さえ守れば、それほど難しくありません。逆に、いきなり全件に対して書き込みをかけるのが一番危険です。
つまずきの多くは、コードの書き方ではなくサーバー側の防御(WAF・セキュリティプラグイン)が原因です。エラーが出たら「自分のコードが間違っている」と決めつけずに、レスポンスのステータスコードを確認するところから始めると、解決が早くなります。同じ考え方は、WordPress以外のAPIを扱うときにもそのまま使えます。