はじめに
前回は、WordPressから取得した記事一覧 posts_export.csv をもとに、記事ごとのカテゴリー分類案を作りました。
作成したファイルは以下です。
data/categorized_posts.csv
このCSVには、記事ごとに以下のような情報が入っています。
post_id
title
slug
main_category
sub_category
tags
confidence
reason
今回は、この見直し済みCSVを使って、WordPress本体にカテゴリー・タグを反映します。
また、途中で見つかった「slugが長すぎる問題」も整理します。
今回やることは以下です。
1. categorized_posts.csv を確認する
2. WordPress側にカテゴリー・タグを作成または照合する
3. 5件だけdry-runする
4. 5件だけ実際に反映する
5. 問題なければ全件反映する
6. slugを25文字以内に整理する
7. AGENTS.mdで安全ルールを管理する
ここからはWordPress本体を更新します。
そのため、これまで以上に安全設計を重視しました。
前提
作業ディレクトリは以下です。
cd ~/wp-codex-tools
source .venv/bin/activate
.env には、WordPress REST API用の接続情報が入っています。
WP_BASE_URL=https://www.example.com
WP_USERNAME=your_wordpress_username
WP_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
また、前回までに以下のファイルができている前提です。
data/posts_export.csv
data/categorized_posts.csv
今回の入力になるのは、主に data/categorized_posts.csv です。
まずCSVを確認する
WordPressに反映する前に、CSVが存在するか確認します。
ls -lh data/categorized_posts.csv
中身を少し見ます。
head -n 5 data/categorized_posts.csv
件数も確認します。
python - <<'PY'
import pandas as pd
df = pd.read_csv("data/categorized_posts.csv")
print(f"分類済み記事数: {len(df)}件")
print()
print(df["main_category"].value_counts())
print()
print(df["confidence"].value_counts())
PY
ここで、カテゴリーの分布や confidence=low の件数を確認しておきます。
私の場合は、以下の7カテゴリに分類しました。
旅の設計
ポイント・マイルをためる
旅行記
クレカ・保険
通信・スマホ
家族・暮らしのデザイン
思考のデザイン
カテゴリーは少なめにして、細かいテーマはタグで拾う方針です。
WordPressに反映する前の安全ルール
今回のように、WordPress本番環境をAPIで更新する場合、いきなり全件反映するのは怖いです。
そこで、最初から以下のルールにしました。
- デフォルトはdry-run
- --apply を付けたときだけ更新する
- 最初は --limit 5 で5件だけ試す
- 本文、タイトル、公開状態は変更しない
- カテゴリーとタグだけ更新する
- 更新前に対象記事を表示する
- APIパスワードは絶対に表示しない
これらのルールは AGENTS.md にも書いておきます。
## 絶対ルール
- `.env` の中身を表示しない。
- `WP_APP_PASSWORD` をログに出さない。
- WordPressへの更新処理は、必ず dry-run をデフォルトにする。
- `--apply` が指定されたときだけWordPressを更新する。
- 最初から全件更新しない。必ず `--limit 5` でテストできるようにする。
- 本文、タイトル、slug、公開状態、excerpt は変更しない。
- 変更してよいのは `categories` と `tags` のみ。
- 削除系APIは実装しない。
- 既存カテゴリー・タグがある場合は再利用する。
- 新規作成する場合は、作成前に一覧表示する。
- すべての更新前に対象 post_id, title, category, tags を表示する。
- APIエラー時も認証情報は表示しない。
この AGENTS.md が、後でかなり効きました。
Codexにカテゴリー・タグ反映処理を作ってもらう
当初は、以下のようにファイルを分ける想定でした。
sync_taxonomies.py
カテゴリー・タグの作成・照合
apply_categories.py
投稿へのカテゴリー・タグ反映
ただ、実際にはCodexが sync_taxonomies.py に反映処理までまとめて実装してくれました。
つまり、最終的には以下のような使い方になりました。
python scripts/sync_taxonomies.py --limit 5
python scripts/sync_taxonomies.py --limit 5 --apply
python scripts/sync_taxonomies.py --all
python scripts/sync_taxonomies.py --all --apply
最初に想定していた apply_categories.py は存在しませんでした。
実行したところ、以下のようなエラーが出ました。
python: can't open file 'scripts/apply_categories.py': [Errno 2] No such file or directory
ただし、これは危険なエラーではありません。
apply_categories.py がないため、WordPressへの更新は実行されていません。
Codex側からは、同等処理は以下で実装済みだと案内されました。
python scripts/sync_taxonomies.py --limit 5 --apply
こういうズレが起きることもあるので、実装されたスクリプトの --help を確認するのは大事です。
python scripts/sync_taxonomies.py --help
まずdry-runする
いきなり反映せず、まず5件だけdry-runします。
python scripts/sync_taxonomies.py --limit 5
この段階では、WordPressは更新されません。
表示される内容は、以下のようなイメージです。
DRY RUN
post_id: 123
title: 楽天キャッシュ積立をマイル発生装置にする方法
category: ポイント・マイルをためる
tags: 楽天キャッシュ | ANA Pay | 楽天Edy | ANAマイル
post_id: 124
title: どこかにマイルで福岡へ行った話
category: 旅行記
tags: どこかにマイル | 福岡 | 日帰り旅
ここで確認するのは以下です。
- 対象記事が想定どおりか
- main_category が正しいか
- tags が自然か
- 本文やタイトルを更新しようとしていないか
- dry-runになっているか
5件だけ反映する
dry-runの表示に問題がなければ、5件だけ実際に反映します。
python scripts/sync_taxonomies.py --limit 5 --apply
ここで初めてWordPress本体が更新されます。
ただし、対象は5件だけです。
反映後は、WordPress管理画面で確認します。
投稿一覧
→ 該当記事
→ カテゴリー
→ タグ
ここで確認することは以下です。
- カテゴリーが反映されているか
- タグが反映されているか
- 本文が変わっていないか
- タイトルが変わっていないか
- 公開状態が変わっていないか
問題なければ、次に全件反映へ進みます。
全件反映の前にもdry-runする
全件反映の前に、もう一度dry-runします。
python scripts/sync_taxonomies.py --all
この段階でも、WordPressは更新されません。
全件の反映予定を確認します。
問題なければ、全件反映します。
python scripts/sync_taxonomies.py --all --apply
100記事程度であれば、処理量としてはそれほど大きくありません。
ただし、本番環境への更新なので、5件テストを挟むだけで安心感がかなり違います。
実際のコマンドまとめ
最終的に、カテゴリー・タグ反映で使ったコマンドは以下です。
cd ~/wp-codex-tools
source .venv/bin/activate
python scripts/sync_taxonomies.py --limit 5
python scripts/sync_taxonomies.py --limit 5 --apply
python scripts/sync_taxonomies.py --all
python scripts/sync_taxonomies.py --all --apply
当初想定していた apply_categories.py ではなく、実装済みの sync_taxonomies.py に合わせて進めました。
このあたりは、Codexに作ってもらったファイル構成をよく確認する必要があります。
slugが長すぎる問題
カテゴリー・タグ整理と並行して、もう1つ気になったのがslugです。
WordPressでは、日本語タイトルからslugが作られると、URL上でかなり長くなることがあります。
たとえば、日本語slugはブラウザ上では読めても、実際のURLでは以下のようにエンコードされます。
%E3%81%93%E3%82%8C%E3%81%AF%E3%81%99%E3%81%94%E3%81%8F%E9%95%B7%E3%81%84...
これはかなり見づらいです。
そこで、slugは英数字とハイフンだけにして、短く整えることにしました。
最終的なルールは以下です。
- 英小文字、数字、ハイフンのみ
- 日本語は使わない
- アンダースコアは使わない
- 連続ハイフンは禁止
- 先頭・末尾のハイフンは禁止
- 25文字以内
- 原則2〜4語
- 意味を説明しすぎない
slugは記事タイトルの代わりではなく、URL上の住所だと考えることにしました。
記事タイトルが説明してくれるので、slugは短くて安定している方がよいです。
slug候補を作る
slug整理では、まず候補CSVを作るだけにしました。
作成するスクリプトは以下です。
scripts/suggest_slugs.py
出力ファイルは以下です。
data/slug_suggestions.csv
Codexには、以下のように指示しました。
scripts/suggest_slugs.py の slug生成ルールを変更してください。
変更内容:
- suggested_slug は必ず25文字以内にする
- 25文字を超えるslugは出力しない
- 原則 2〜4語にする
- 英小文字、数字、ハイフンのみ使用する
- 日本語は使わない
- アンダースコアは使わない
- 連続ハイフンは禁止
- 先頭・末尾のハイフンは禁止
- 同じslugが出た場合は、25文字以内に収まるように末尾へ -2, -3 のような番号を付ける
- 番号を付けても25文字を超える場合は、末尾の単語を短縮または削除して25文字以内に収める
まず20件だけ確認します。
python scripts/suggest_slugs.py --limit 20
よさそうなら全件生成します。
python scripts/suggest_slugs.py --all
25文字を超えるslugが残っていないか確認します。
python - <<'PY'
import pandas as pd
df = pd.read_csv("data/slug_suggestions.csv")
too_long = df[df["suggested_slug"].astype(str).str.len() > 25]
print(f"25文字超え: {len(too_long)}件")
if len(too_long):
print(too_long[["post_id", "title", "suggested_slug"]].to_string(index=False))
PY
25文字超え: 0件 ならOKです。
slug変更でAGENTS.mdが止めてくれた
ここで面白いことが起きました。
slugを更新する apply_slugs.py を作ろうとしたところ、Codexが止まりました。
理由は、AGENTS.md に以下のルールがあったからです。
- 変更してよいのは categories と tags のみ
- slug は変更しない
そのため、Codexは以下のように返しました。
現在の AGENTS.md と衝突するため実装できません。
絶対ルールに以下があります。
- 変更可能なのは categories と tags のみ
- slug は変更しない
--apply でslugを更新する実装はこのルール違反になります。
slug更新を許可するよう AGENTS.md を変更する指示を明示してください。
これはかなり良い挙動でした。
AIエージェントが勝手に本番環境のslugを変えようとせず、プロジェクトの安全ルールを読んで止まったわけです。
この時点で、AGENTS.md を書いておいてよかったと思いました。
slug整理専用の例外ルールを追加する
とはいえ、今回はslug整理もしたかったので、通常ルールを崩すのではなく、専用スクリプトに限って例外を許可することにしました。
AGENTS.md に以下のようなルールを追加します。
## slug整理モードの例外ルール
通常運用では、変更してよいのは `categories` と `tags` のみとする。
ただし、以下の専用スクリプトに限り、`slug` の変更を許可する。
- scripts/suggest_slugs.py
- scripts/apply_slugs.py
slug変更を許可する条件:
- `scripts/suggest_slugs.py` は候補CSVを作るだけで、WordPress APIを更新してはいけない
- `scripts/apply_slugs.py` は `slug` のみ更新してよい
- `scripts/apply_slugs.py` でもデフォルトは必ず dry-run にする
- `--apply` が指定された場合だけWordPressを更新する
- `--limit 5` で少数テストできるようにする
- 更新してよいフィールドは `slug` のみ
- 本文、タイトル、status、excerpt、categories、tags は変更しない
- 変更前に post_id, title, current_slug, suggested_slug, old_url, new_url を表示する
- 変更後に redirect_map.csv を必ず出力する
- 削除系APIは実装しない
- `.env` の中身を表示しない
- `WP_APP_PASSWORD` をログに出さない
ポイントは、全体ルールをゆるめないことです。
通常運用では、依然として categories と tags のみ変更可能。
slug変更は、apply_slugs.py に限って許可する。
こうしておくことで、Codexに対して「今回だけの例外」を明示できます。
slugを反映する
slug候補CSVを確認したら、次に反映用スクリプトを使います。
scripts/apply_slugs.py
まずdry-runです。
python scripts/apply_slugs.py --limit 5
問題なければ、5件だけ反映します。
python scripts/apply_slugs.py --limit 5 --apply
確認後、全件反映します。
python scripts/apply_slugs.py --apply
このとき、redirect_map.csv も出力するようにしました。
data/redirect_map.csv
中身は以下のような形式です。
post_id,title,old_url,new_url,current_slug,suggested_slug
slugを変えるとURLが変わります。
そのため、旧URLと新URLの対応表を残しておくことは重要です。
必要に応じて、WordPressのリダイレクトプラグインなどに取り込める形にできます。
apply_slugs.pyにも防御チェックを入れる
slug候補を作る側で25文字以内にしていても、反映側でもチェックするようにしました。
apply_slugs.py 側の安全チェックは以下です。
- suggested_slug が25文字を超える場合はスキップ
- 英小文字、数字、ハイフン以外を含む場合はスキップ
- 先頭または末尾がハイフンの場合はスキップ
- 連続ハイフンが含まれる場合はスキップ
- 空欄ならスキップ
- current_slug と suggested_slug が同じならスキップ
つまり、候補生成側と反映側の両方でチェックします。
これは少し冗長ですが、本番環境を更新する処理では、このくらいでちょうどいいと思っています。
今回の流れ全体
最終的な流れは以下です。
cd ~/wp-codex-tools
source .venv/bin/activate
# カテゴリー・タグ反映
python scripts/sync_taxonomies.py --limit 5
python scripts/sync_taxonomies.py --limit 5 --apply
python scripts/sync_taxonomies.py --all
python scripts/sync_taxonomies.py --all --apply
# slug候補作成
python scripts/suggest_slugs.py --all
# 25文字超えチェック
python - <<'PY'
import pandas as pd
df = pd.read_csv("data/slug_suggestions.csv")
too_long = df[df["suggested_slug"].astype(str).str.len() > 25]
print(f"25文字超え: {len(too_long)}件")
if len(too_long):
print(too_long[["post_id", "title", "suggested_slug"]].to_string(index=False))
PY
# slug反映
python scripts/apply_slugs.py --limit 5
python scripts/apply_slugs.py --limit 5 --apply
python scripts/apply_slugs.py --apply
この流れなら、カテゴリー・タグもslugも、いきなり全件更新せずに進められます。
AIエージェントに本番環境を触らせるときに大事だと思ったこと
今回やってみて、いちばん大事だと感じたのは、Codexに「何をさせるか」だけでなく、「何をさせないか」を書くことです。
特に重要だったのは以下です。
- dry-runをデフォルトにする
- --apply がないと更新しない
- --limit 5 で小さく試せるようにする
- 更新してよいフィールドを限定する
- 削除系APIは実装しない
- 認証情報を表示しない
- redirect_map のような戻るための情報を残す
- AGENTS.mdでルールを明文化する
Codexはかなり便利ですが、本番環境に対しては慎重に使う必要があります。
今回、slug変更でCodexが止まったのはかなり象徴的でした。
安全ルールを書いておけば、AIエージェントはそれを読んで止まってくれる。
これは、単に作業を自動化するだけでなく、安全な作業環境を作るうえで重要だと感じました。
できたこと
今回できたことは以下です。
- 見直し済みの categorized_posts.csv をWordPressに反映した
- カテゴリー・タグをWordPress側に作成・照合した
- dry-runで確認してから反映した
- --limit 5 で少数テストした
- 問題なければ全件反映した
- slugを25文字以内の英数字に整理した
- slug変更用の redirect_map.csv を作った
- AGENTS.mdで通常ルールと例外ルールを分けた
これで、WordPress記事100本の整理がかなり進みました。
管理画面で1記事ずつポチポチしていたらかなり時間がかかる作業ですが、CSVとCodexとWordPress REST APIを組み合わせることで、かなり現実的に進められました。
まとめ
今回のシリーズでは、Codex CLIとWordPress REST APIを使って、WordPress記事100本を整理しました。
全体の流れは以下です。
第1回:Codex CLIでWordPress整理環境を作る
第2回:WordPress REST APIで記事をCSVにエクスポートする
第3回:Codexでカテゴリー分類案を作る
第4回:カテゴリー・タグ・slugを安全に一括反映する
やってみて感じたのは、これは単なるWordPress整理ではないということです。
WordPress管理画面で手作業するのではなく、記事群をCSVとして扱い、Codexにスクリプトを作らせ、人間が確認し、本番環境へ段階的に反映する。
この流れができると、非エンジニアでもかなり高度なサイト運用ができるようになります。
ただし、便利さと危険さはセットです。
AIエージェントに本番環境を触らせるなら、最初に安全柵を作ることが大事です。
今回でいうと、それは以下でした。
AGENTS.md
dry-run
--apply
--limit 5
更新対象フィールドの限定
redirect_map.csv
このあたりをきちんと用意すれば、Codex CLIはWordPress運用のかなり強力な相棒になります。
記事が増えたあとに整理するのは大変ですが、逆に言えば、記事が増えてからこそCodexの出番があるのだと思いました。