ブログ一覧

WordPress 移行後に本文に残った旧 URL を Portable Text から数える

更新: Web開発者向け

WordPress から Astro 製の CMS「EmDash」 に移行したあと、記事本文が旧サイトの画像 URL を指したまま残ることがあります。画像自体は移行時に取り込まれているので表示は壊れず、旧 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 の中の srchref が素通りしていました。以前、別サイトで同じ形の取りこぼしに当たっており、移行ツールを変えても同じ場所に溜まることが分かります。

見つけ方は単純で、本文データを走査するときに「画像ブロック」「リンク注釈」だけでなく「HTML を持つブロック」も見ることです。

一括置換してはいけない

旧 URL を機械的に置換したくなりますが、それをやると文章が壊れます。実例です。

予期せぬエラーが発生した場合、public_html/wp-content/uploads のフォルダの中に
「bk_」から始まるフォルダが残ります。

WordPress の解説記事なので、wp-content/uploadsURL ではなくディレクトリ名として本文に出てきます。ここを置換すると、意味の通らない文章になります。

対象を「URL が入る場所」に限定するのが安全です。具体的には、画像ブロックの参照先、画像ブロックのリンク先、HTML ブロックの中身、リンク注釈の 4 つ。文字そのもの(span の text)は触らない、と決めました。

サイトをまたいだ参照が混ざっていた

作業中に、片方のサイトの記事が 別のサイトの画像を参照している箇所が 22 見つかりました。吹き出しのアイコン画像です。同じ人が両方を運営していた時期に、片方から手軽に引っ張ったのだと思います。

そして 9 個のファイル名を照合すると、全部が自サイト側にも取り込まれていました。相手のサイトを参照する理由はもう無いので、自サイトのファイルに向け直しました。

さらに、そのうち 2 ファイルは相手側に存在しませんでした。

$ curl -sI https://other.net/wp-content/uploads/2016/12/batsu.jpg
HTTP/2 404

5 記事で画像が壊れていました。 転送の仕組みがあっても、転送先にファイルが無ければ 404 です。読者には壊れた画像として見えていたはずですが、公開 HTML の目視では気づけていませんでした。片付けのついでに直りました。

書き換えの手順で踏んだ 2 つの落とし穴

CLI 経由で本文を書き換えました。ここでも 2 回止まりました。

1 つ目。生データで読み書きしないと壊れます。 CLI は既定で構造化本文と markdown を相互変換します。読みやすい代わりに、生 HTML ブロックのような変換規則の外にあるものが通ると危険です。読みも書きも --raw を付けて、構造化されたまま扱いました。

2 つ目。取得結果には下書きと公開中の両方が入っています。 書き換え後に照合したら「全記事で失敗」と出て、しばらく原因を探しました。実際には成功していて、同じ JSON に含まれる公開中のデータ(まだ古い)を数えていただけでした。照合するときは、下書き側だけを見る必要があります。

流れとしてはこうなります。

  • データベースで対象記事の一覧を取る
  • 記事ごとに生データを取得する
  • 4 種類の場所だけを書き換える
  • 下書きとして保存し、下書き側だけを見て「旧 URL 0 件」を確認する
  • 公開する

2 サイト合わせて 78 記事を処理し、公開ページを全数スキャンして旧 URL が 0 件になったことを確認しました。データベース側に 1 件だけ残っていますが、これは前述の「ディレクトリ名として書かれた文章」です。

転送の仕組みは消せるのか

本文から旧 URL が消えたので、転送用のルートも消せそうに見えます。実際に測って判断しました。

書き換えが終わった直後の 20 分間で、旧 URL へのアクセスがこれだけありました。

  • 一方のサイト: 4 件(すべて実在する画像への転送)
  • もう一方: 5 件(うち 4 件が実在する画像)

自サイトの記事はもう旧 URL を出していないので、これは全部が外部由来です。 検索エンジンの画像インデックス、他サイトからの被リンク、旧 URL を記憶しているクローラー。どれもこちらの管理外にあり、書き換えでは消せません。

1 日あたりに直すと 300 件前後になります。転送を消すとこれが全部 404 になり、外部から来た人に画像が出なくなります。消すのはまだ早い、という結論になりました。

判断を実測で置き換えられたのは収穫でした。「外部リンクがあるかもしれないから残す」は永久に消せない理由になりますが、「1 日 300 件来ているから残す」は、ゼロに近づいたときに消せる理由になります。

まとめ

  • 公開 HTML を検索するだけでは、移行の残骸は見つけきれない。表示されないデータが残る
  • 数えるならコンテンツのデータベースを見る。差が出たら、その差の正体を説明できるまで進めない
  • 書き換え漏れは生 HTML ブロックに溜まる
  • 一括置換は避ける。URL が入る場所だけを対象にする
  • 転送の仕組みを消してよいかは、実際のアクセス数で決められる

関連

この記事をシェア