重複記事を301で統合する手順|退避・転送・ゴミ箱・検証を自動化する
同じテーマの記事が2本あって、どちらも中途半端に順位を取っている。削除していいのか、残すならどちらか、迷って手が止まる。これは大規模修正のうちでも、最初に片づけたい構造の問題です。
結論から書きます。重複記事は、残す側を1本に決め、消す側は「本文の退避 → 301リダイレクト → ゴミ箱に送る → 転送が効いているか検証」の4手順で統合します。 この4つを一つのスクリプトにまとめ、少ない件数で試し実行してから本番で動かします。削除ではなくゴミ箱に送るのは、間違えたときに戻せる状態を残すためです。
この記事は、散らかったサイトをAIで作り変えるマニュアルの「大規模修正」の作業です。1記事ずつのリライトに入る前に、ここで消える記事を先に消しておきます。
この作業で決まること
この作業が終わると、重複した2本以上の記事が、1本の正しいURLに集まり、消した側のURLに来た読者と検索エンジンのクロールが残した側へ届く状態 になります。
- 消す側の記事は WordPress のゴミ箱に入ります(完全削除ではありません)
- 消す側のURLは301で残す側へ転送されます
- 消す側の本文はローカルにファイルとして残ります
- 転送が効いていることを、HTTPのステータスと
Locationヘッダで確認します
転送より先に消す側をゴミ箱へ送ると、短い時間ですが消す側のURLが404を返す窓ができます。順番は「退避 → 転送を有効にする → 消す側をゴミ箱に送る」の順で固定し、スクリプトの中でも入れ替えません。
前提:この記事の前に必要なもの
- AIにサイトを操作させる準備(WordPress REST API)が終わっていること
- 全記事の台帳から、統合対象の重複ペアが特定できていること
- Redirection プラグインが入っていて、REST API(
/wp-json/redirection/v1/redirect)が応答すること - 退避ファイルを保存するフォルダ(
rewrite_backups/など)があること
Redirection プラグインの公式サイトは https://redirection.me/ です。REST API の形はプラグインのバージョンで変わるので、本番の前に自分のサイトで1件だけ投げて、返ってくるJSONを目で確かめます。
AIに渡すもの
- 統合ペアの一覧:(消す側の記事ID・消す側のスラッグ・残す側の記事ID・残す側のスラッグ)の組を、プログラムの中に配列として持つ
- WordPress の認証情報:REST API のユーザーとアプリケーションパスワード
- 退避先のフォルダのパス
- 実行モードの切り替え:引数で「試し実行」と「本番実行」を分ける
ペアの一覧はコードの中に書き、AIにペアを毎回自分で判断させません。重複の判定は、同じテーマで複数URLが存在するという構造の問題で、ここは人が決めてから渡します。自分のサイトで作ったスクリプトでは、ペアを MERGES という配列として (trash_id, trash_slug, keep_id, keep_slug) の形で並べ、--apply を付けたときだけ本番の操作が走る作りにしています。
手順
- ペアを決める。残す側は、公開日が古い・被リンクがある・順位が高い・本文が厚い、のどれかで決めます。迷うときは、残す側に本文を寄せる作業を先に済ませてから統合します。
- 試し実行する。スクリプトを
--apply無しで実行し、ペアの件数・消す側の記事タイトル・ステータスが期待どおりかを画面で確認します。この段階ではサイト側は何も変わりません。 - 1件だけ本番で動かす。配列から1ペアだけ残して
--applyを付け、退避ファイルが保存されたか、Redirection プラグインに301が1件増えたか、消す側の記事のステータスがtrashになったかを確認します。 - 残りを本番で動かす。1件が通ったら残りのペアを配列に戻し、もう一度
--applyで実行します。1回に流す件数は3〜5件までに抑えます。 - 転送の検証。消す側のURLに
allow_redirects=Falseでアクセスし、ステータスが301、Locationヘッダが残す側のURLになっていることを確認します。この検証はスクリプトの最後にまとめて走らせます。 - Search Console でURL検査。数日たってから、消す側のURLをURL検査にかけ、「正規URL」が残す側に切り替わっているかを見ます。
最初の本番実行で1件に絞ると、問題が起きたときに原因の切り分けが短くて済みます。1回に数十件を流すと、Redirection プラグイン側の内部整合やキャッシュがずれたときに、どのペアで止まったのか追うのに時間がかかります。
完了条件
- ☐ 消す側の記事の本文がローカルに退避されている(
記事ID_日付.raw.htmlの形で保存) - ☐ Redirection プラグインに、消す側URL → 残す側URL の301が登録されている
- ☐ 消す側の記事が WordPress のゴミ箱(
status: trash)に入っている - ☐ 消す側URLへのアクセスが、HTTP 301 と
Location: 残す側URLを返す - ☐ 統合したペアを、作業記録に残している(日付・記事ID・残した側)
実際に起きた失敗
このスクリプトは、本番サイトを機械で触るときに取り返しのつかない状態を作らないよう、設計の段階で3つの決まりを入れています。守らないと戻せなくなるという理由で入れた決まりなので、ここが崩れていないかを公開前に見ます。
- 完全削除ではなく、ゴミ箱に送る。WordPress の REST API では、
DELETE /posts/{id}にforce=Falseを渡すとステータスがtrashになり、管理画面から復元できます。自分のスクリプトではparams={"force": False}を固定にして、force=Trueを使わない作りにしています。間違えたペアを統合しても、ゴミ箱から戻せます。 - 本文を先に退避する。ゴミ箱からの復元は WordPress 側の仕様に依存するので、復元できなかった場合に備えて、本文のHTMLをファイルで持ちます。退避ファイル名は
記事ID_日付.raw.htmlの形にして、同じ記事を別の日に触っても上書きされないようにしています。 - 転送と削除の順序を固定する。コードの中で「退避 → 301登録 → ゴミ箱」の順を変えません。301を登録する前にゴミ箱に送ると、短い時間ですが404が返る窓ができるので、ここは入れ替えない構造にしています。
試し実行の仕組みも、同じ理由で入れています。デフォルトは何もしない動作で、--apply を明示したときだけ本番の操作が走ります。本番サイトを変更するスクリプトは、明示しないと動かない ことを前提にすると、別の作業中に誤って走らせても被害が出ません。
よくある質問
Q. 301ではなく、残す側に本文を追記して、消す側は手動で削除してはいけませんか?
A. 本文を寄せる作業自体は必要なことがあります。ただし、消す側のURLに来る被リンクや、検索結果に残っているキャッシュを捨てることになるので、転送は別に入れます。削除と転送はセットです。
Q. 残す側のスラッグが読みにくいです。残す側のURLを変えてもいいですか?
A. 残す側のURLを変えると、残す側にも301を足すことになり、301が2段になります。1段で済むように、残す側のURLは触らず、「どちらがより良いスラッグか」ではなく「どちらが実力でクリックを取っているか」で残す側を決めます。
Q. Redirection プラグインを使わず、サーバーの設定ファイルで301を書いてはいけませんか?
A. 書けますが、WordPress の記事ID・スラッグと転送ルールが別の場所に分かれると、台帳との整合を機械で確かめにくくなります。REST API で登録・取得できる Redirection プラグインを使うと、転送の一覧も台帳と同じ方法で読めます。
関連記事
- サイトに必要なのは大規模・中規模・小規模どの修正か
- AIにサイトを操作させる準備(WordPress REST API)
- 全記事の台帳をライブで作る
- AIに既存記事をリライトさせると劣化する理由と、外科手術に分解する手順
次の作業
- カニバリを検出して、統合か差別化かを判定する(中規模修正)
- カテゴリと分類を作り直す(大規模修正)
- 1記事のリライトを外科手術に分解する(小規模修正)