WordPress 移行後に本文に残った旧 URL を Portable Text から数える
WordPress から Astro 製の CMS「EmDash」 に移行したあと、記事本文が旧サイトの画像 URL を指したまま残ることがあります。画像自体は移行時に取り込まれているので表示は壊れず、旧 URL から新しい場所へ転送する仕組みを置いておけば読者は気づきません(WordPress 移行後に記事内の画像が 404 になるときの対処)。
残った旧 URL を片付けようとして、対象を数え間違えました。公開されている HTML を見て数えた本数とデータベースを見て数えた本数が、3 倍近く違いました。
まず実測
公開ページを全部取得して、本文に旧 URL が残っているものを数えます。
$ curl -s https://example.net/sitemap-posts.xml | grep -oE '<loc>[^<]+</loc>' | sed 's/<[^>]*>//g' \
| xargs -P 8 -I{} sh -c 'n=$(curl -s "{}" | grep -oE "wp-content/uploads/[^\"]+" | sort -u | wc -l); [ "$n" -gt 0 ] && echo "$n {}"'
21 本次に、コンテンツを保存しているデータベースを直接見ます。
$ wrangler d1 execute example-db --remote \
--command "select count(*) from ec_posts where content like '%wp-content/uploads%' and deleted_at is null"
58 本37 本の差が出ました。公開ページを見て「21 本を直せば終わり」と判断していたら、半分以上を残したまま完了と報告するところでした。
差の正体は表示されないリンクだった
差分の記事を 1 本開いて、本文データのどこに旧 URL があるかを調べると、構造化された本文(Portable Text)のリンク注釈に入っていました。
{
"_type": "block",
"children": [{ "_type": "span", "text": "「アクセシビリティ」をタッチ。" }],
"markDefs": [{ "_type": "link", "_key": "key-36-9hp0q", "href": "https://example.net/wp-content/uploads/2017/01/accessibility.jpg" }]
}リンクの定義(markDefs)はありますが、そのリンクを適用している文字がありません。文字側の marks にこのキーが入っていないので、HTML に出力されるときには何も起きません。
このリンク定義は、WordPress の「画像をメディアファイルにリンク」で作られたリンクの残りでした。移行時に画像が独立したブロックに変換され、リンクの定義だけが本文データに取り残されていたわけです。
このリンクは表示には出てこないのにデータとしては残っているので、公開 HTML を検索する方法では原理的に見つかりません。1 サイトで 121 箇所、もう 1 サイトで 136 箇所ありました。
書き換え漏れは生 HTML ブロックに溜まる
もう 1 つの見落としは、生の HTML が入ったブロックでした。
{
"_type": "htmlBlock",
"html": "<div class=\"balloon\"><figure><a href=\"https://example.net/wp-content/uploads/2016/03/eigyouman-2.png\"><img src=\"https://example.net/wp-content/uploads/2016/03/eigyouman-2.png\"></figure></div>"
}移行ツールの URL 書き換えは markdown 記法の画像を対象にしていたため、生 HTML の中の src と href が書き換えられずに残っていました。以前別サイトでも同じ形で書き換え漏れが出ていたので、移行ツールを変えても書き換え漏れは生 HTML ブロックに残ることが分かります。
見つけ方は単純で、本文データを走査するときに「画像ブロック」「リンク注釈」だけでなく「HTML を持つブロック」も見ることです。
旧 URL を一括置換すると本文の文章が壊れる
旧 URL を機械的に置換すると、次のような文章が壊れます。
予期せぬエラーが発生した場合、public_html/wp-content/uploads のフォルダの中に
「bk_」から始まるフォルダが残ります。WordPress の解説記事なので、wp-content/uploads が URL ではなくディレクトリ名として本文に出てきます。ここを置換すると、意味の通らない文章になります。
対象を「URL が入る場所」に限定するのが安全で、具体的には画像ブロックの参照先、画像ブロックのリンク先、HTML ブロックの中身、リンク注釈の 4 つです。文字そのもの(span の text)は触らないと決めました。
サイトをまたいだ参照が混ざっていた
作業中に、一方のサイトの記事が 別のサイトの画像を参照している箇所が 22 見つかり、どれも吹き出しのアイコン画像でした。同じ人が両方を運営していた時期に、もう一方のサイトの画像を手軽に参照したのだと思います。
そして 9 個のファイル名を照合すると、全部が自サイト側にも取り込まれていました。相手のサイトを参照する理由はもう無いので、自サイトのファイルに向け直しました。
さらに、そのうち 2 ファイルは相手側に存在しませんでした。
$ curl -sI https://other.net/wp-content/uploads/2016/12/batsu.jpg
HTTP/2 4045 記事で画像が 404 になっていました。 転送の仕組みがあっても、転送先にファイルが無ければ 404 です。読者には壊れた画像として見えていたはずですが、公開 HTML の目視では気づけていませんでした。自サイトのファイルに向け直したことで、この 5 記事の画像も表示されるようになりました。
書き換えの手順で踏んだ 2 つの落とし穴
CLI 経由で本文を書き換える手順でも、作業が 2 回止まりました。
1 つ目は、生データで読み書きしないと本文が壊れることです。 CLI は既定で構造化本文と markdown を相互変換します。読みやすい代わりに、生 HTML ブロックのような変換規則の外にあるものが通ると危険です。読みも書きも --raw を付けて、構造化されたまま扱いました。
2 つ目は、取得結果に下書きと公開中の両方が入っていることです。 書き換え後に照合したら「全記事で失敗」と出て、しばらく原因を探しました。実際には成功していて、同じ JSON に含まれる公開中のデータ(まだ古い)を数えていただけでした。照合するときは、下書き側だけを見る必要があります。
流れとしてはこうなります。
- データベースで対象記事の一覧を取る
- 記事ごとに生データを取得する
- 4 種類の場所だけを書き換える
- 下書きとして保存し、下書き側だけを見て「旧 URL 0 件」を確認する
- 公開する
2 サイト合わせて 78 記事を処理し、公開ページを全数スキャンして旧 URL が 0 件になったことを確認しました。データベース側に 1 件だけ残っていますが、これは前述の「ディレクトリ名として書かれた文章」です。
外部から旧 URL へのアクセスが来るうちは転送の仕組みを消さない
本文から旧 URL が消えたので、転送用のルートも消せそうに見えるので、旧 URL へのアクセスを実際に測って判断しました。
書き換えが終わった直後の 20 分間で、旧 URL へのアクセスがこれだけありました。
- 一方のサイト: 4 件(すべて実在する画像への転送)
- もう一方: 5 件(うち 4 件が実在する画像)
自サイトの記事はもう旧 URL を出していないので、これは全部が外部由来です。 検索エンジンの画像インデックス、他サイトからの被リンク、旧 URL を記憶しているクローラーはどれもこちらの管理外にあり、書き換えでは消せません。
1 日あたりに直すと 300 件前後になります。転送を消すとこの 300 件が全部 404 になり外部から来た人に画像が出なくなるので、転送の仕組みはまだ消さないと決めました。
残す理由を推測から実測の数に変えられたのは収穫でした。「外部リンクがあるかもしれないから残す」は永久に消せない理由になりますが、「1 日 300 件来ているから残す」は、ゼロに近づいたときに消せる理由になります。
まとめ
- 公開 HTML を検索するだけでは、移行で残った旧 URL を見つけきれない。表示されないリンク注釈にも旧 URL が残る
- 数えるならコンテンツのデータベースを見る。差が出たら、その差の正体を説明できるまで進めない
- 書き換え漏れは生 HTML ブロックに溜まる
- 一括置換は避け、URL が入る場所だけを対象にする
- 転送の仕組みを消してよいかは、実際のアクセス数で決められる