WordPressバックアップから記事を復元できるか検証し、専用リポジトリを立てた日

開発mdx-playground

WordPressバックアップから記事を復元できるか検証し、専用リポジトリを立てた日

パソコンのどこかにWordPressのバックアップを作った記憶はあるのに、保存先を思い出せない。Cドライブなのかドロップボックスなのかも怪しい。手元にあるのは、まだダウンロードが終わっていない圧縮ファイルだけで、その中身が本当に記事として蘇るのかは、この時点ではまだ分からなかった。

いま使っているWordPress用の高速化ホスティングサービスをそろそろ解約したいと思っている。ただ解約する前に、過去の記事が読めなくなるのは困る。データベースのSQLダンプさえあれば記事本文は取り出せそうだが、問題は画像や動画だ。本文と一緒にアップロードフォルダも同じバックアップに含まれているのか、含まれていたとして本当に紐づけ直せるのか。ここは実際に手を動かして確かめるしかなかった。

忘れていたバックアップの中に「本命」を見つける

ユーザーフォルダとドロップボックス・ダウンロードフォルダをまとめて探させたら、日付の近いフォルダの中にwordpress_db.sql(約500MB)が見つかった。中身はテーブル定義とINSERT文が数千行に集約された、素のmysqldump形式だった。

既製のパーサーには頼らず、自作のスクリプトでINSERT文を読み解かせた。結果、公開記事210件をJSONとして取り出すことに成功した。バックアップの存在自体は覚えていたのに、記事の総数が210件だったことには少し驚いた。もっと少ない気がしていた。

壊れた185MBと、本物の8.8GB

数字が取れただけでは安心できない。画像や動画とセットになった記事も同じように蘇るのかを、実物で試すことにした。

最初に手をつけたのは185MB程度のtar.gzだった。展開してみると、含まれているのは2016年8月分のアップロードファイルだけ。しかも展開の途中でファイルが途切れる。gzipの整合性チェックでも壊れていることが分かった。

一方、別途ダウンロードが進んでいた8.8GBのファイルは、整合性チェックが通り、全期間のアップロードが揃っていた。こちらが本物のフルバックアップだった。小さい方だけを頼りにしていたら、大半の画像・動画が復元できないまま終わっていたはずだ。

画像・動画つきの記事を1本、実際にNuxt Content形式へ変換させ、devサーバーで開いて確認した。動画はreadyState: 4(読み込み完了)で正常に再生でき、画像も表示された。ただしGIF画像の上に古い「GIF」というプレースホルダー表示が重なって、クリックするとモーダル拡大が開いてしまう。既存のProseImg.vueにモーダルを無効化するオプションを追加させ、この記事だけその挙動を切らせて直した。

GIFのままでいいのか、動画に変換すべきか

もう一本、書籍サンプル画像をタブで切り替えるUIと、講座紹介のブロックが入った記事も復元させた。「GIF」というオーバーレイ表示の実装で済むはずだと踏んで進めさせたら、想定通りタブ切り替えも動いた。

ここで立ち止まった。そもそもこのGIFカバー、なぜこんな形にしたんだったか。作った本人なのに理由を思い出せない。改めて考えると、クリックしたら動画が再生されればいいだけで、GIFという画像形式を経由する必然性はない。バックアップの中を探させると、末尾に"_play"の付かない本編ファイル(story_10_Investment_01/02/03.gif)が別に存在していた。

ffmpegでこの3本をmp4に変換させると、ファイルサイズは元のGIFの4〜5分の1に縮んだ。画像として貼っていたものを動画タグに置き換えさせ、新規にProseVideo.vueを作らせた。ところが<video>タグはNuxt ContentのProse自動変換の対象外で、imgが自動的にProseImgになるのと同じようにはいかない。MDC構文で明示的にコンポーネントを呼び出す形に書き直させて、ようやく3箇所とも認識された。動画の上に三角の再生アイコンを重ねるオーバーレイも足させ、見た目だけでも「これは再生できる」と伝わる作りにした。

作業ディレクトリをmdx-playgroundの外に出す

検証用のディレクトリは、最初「Gitリポジトリの直下」という自分の指示のまま、mdx-playgroundの中に作らせていた。ただ会話を振り返ると、この指示は言い切る前に途切れていて、本当はGit_repo直下の独立した場所を意図していた可能性が高い。技術的にも、生データやスクリプトはmdx-playgroundの外に切り出す方が理にかなっている。ブログ本体のリポジトリに、数百MBの生SQLやアップロードファイルを持ち込みたくない。

wordpress-migrationという新規リポジトリをGit_repo直下に立てさせ、検証済みの中身をコピーさせた。コピー自体は完了したものの、mdx-playground側の元ディレクトリを消させようとしたところでWindowsのファイルロックに引っかかり、空のディレクトリ構造だけが残ってしまった。この削除は急がなくていいと判断し、後回しにした。

新しいリポジトリで、210記事と向き合う

分離したwordpress-migrationで作業を再開させ、まず210記事すべてのショートコード使用状況を集計させた。出てきたのは15種類。対応済みの3種類を大きく上回る数で、想定より複雑だと分かった。

記事間リンクの再現も検討させた。旧WordPress側のpost_id・スラッグ・カテゴリの対応表を先に作らせ、記事同士が参照し合っているリンクのうち何件が公開記事210件の範囲内に解決できるかを洗わせた。解決できなかったのは6件。固定ページが4件、下書きが2件だった。プライマリカテゴリの手がかりがpostmeta側に残っていないかも探させたが、見つからなかった。

旧WordPressのカテゴリ階層は、210件中68件(約3割)が複数カテゴリに属していて、そのまま機械的に再現するのは難しい。「旧の構造は自分もよく分かっていなかった」ので、新しいURL構造の設計はまるごと任せることにした。出てきた案は、カテゴリ階層を廃止したフラット構造/eurekapu/{post_name}。post_nameは210件すべてが一意で、リンクの62/68件(91%)がこの形式で解決できた。残る6件は固定ページと下書きで、リンクを外してタイトルだけのテキストに置き換える方針にした。

代表記事の中身を実際に見せると、閉じタグの不整合やショートコードの入れ子など、想定より荒れていることも分かった。ここは変換パイプラインの実装ごと任せ、テスト記事1本を最後まで通して確認する方針で進めさせた。

リンクも表示も、いったんこの中だけで完結させる

変換の途中、#####という記号がそのまま本文に表示されるバグに気づいた。原因は、生HTMLブロックの中に書いた見出しタグを、mdx-playground側のMarkdownパーサーが見出しとして解釈しない仕様だった。バグではなく、パーサーの仕様の壁だった。

このタイミングで、mdx-playground本体への統合はいったん保留し、wordpress-migrationの中だけで完結させる方針に切り替えた。理由はパンくずナビゲーションだった。mdx-playgroundの全コンテンツには共通のパンくずが自動で付き、この移行データも「一般の日記記事と同列」に扱われてしまう。独立した教材群として扱うか日記の仲間として扱うかをまだ決めていなかったのに、中途半端な形で公開ページに紛れ込む形になっていた。

書き込んでしまった検証用ファイルはmdx-playground側から削除させ、wordpress-migration内に簡易的な静的プレビューサーバーを立てさせた。フォントや本文幅は本番サイトの実際のcomputed styleを取得させて合わせ、目次コンポーネントもSVGのアクティブラインごと移植させた。

仕上げの表示確認で、吹き出しアイコンの画像だけが元のサイトのURLのまま読み込まれず止まっていることに気づいた。調べると、この画像だけsrc属性ではなくicon属性で指定されていて、画像URLを拾う正規表現の対象から漏れていた。抽出ロジックを直させて再生成すると、ローカルの画像として読み込めるようになった。8種類のショートコードを含む記事を1本、画像・動画・目次まで含めて最後まで表示確認できたところで、今日の検証は区切りとした。

今日の学び

  • 「壊れているファイル」と「完全なファイル」が同じ名前の系統で2つ存在することがある。サイズやチェックサムを見ずに小さい方だけで判断していたら、大半の画像・動画を復元し損ねていた
  • GIFで動画のように見せていた実装は、当時の理由を自分でも思い出せなかった。作った本人の記憶は当てにならないので、実装を疑うときは「なぜこの形にしたか」まで一度掘り返す価値がある
  • 画像URLの抽出漏れのように、属性名がsrcではないだけで正規表現の網から抜ける事故は起きる。抽出件数の増減(6件→7件)を都度確認していたから気づけた

残った数字

210記事のうち、実際に変換パイプラインを通して表示確認まで済んだのは、まだ1本だけだ。残り209本と、15種類のショートコードのうち残り12種類は、まだ手つかずのままwordpress-migrationに置かれている。