WordPressの記事をMarkdownに変換する|エクスポートしたXMLから画像・商品リンクまで移行した方法

目次
  1. 1. WordPress からエクスポートする
  2. 2. XML を読み込む
  3. 3. HTML を Markdown に変換する
  4. クラシックエディタの記事は「段落タグがない」
  5. ブロックエディタの記事は「コメント」に情報がある
  6. 4. テーマ・プラグイン独自の装飾を移す
  7. Rinker の商品データ
  8. 5. 画像をダウンロードする
  9. 6. 内部リンクと旧 URL を整理する
  10. 変換結果を確認する
  11. まとめ

このブログを WordPress から静的サイト(Astro)に移行するにあたって、一番の大仕事が記事の変換でした。

10年分・200本以上の記事を、WordPress の HTML から Markdown ファイルに変換しています。変換スクリプトは Node.js で書き、何度も実行し直しながら調整しました。

関連記事WordPressをやめてCloudflareに移行した|Astro×Workersでサーバー代0円、記事はMarkdownで書く

1. WordPress からエクスポートする

WordPress の管理画面の「ツール」→「エクスポート」で「すべてのコンテンツ」を選ぶと、XML ファイル(WXR 形式)がダウンロードできます。

この XML には、記事や固定ページだけでなく、次のようなデータも入っています。

データ中身
記事・固定ページタイトル、本文の HTML、公開日、更新日、スラッグ、公開状態
カテゴリ・タグ名前とスラッグ、親子関係
メディアアップロードした画像の URL
カスタム投稿Rinker の商品データ、再利用ブロックなど
カスタムフィールドアイキャッチ画像の ID、閲覧数(プラグインのデータ)など

2. XML を読み込む

WXR は 1 記事が 1 つの <item> になっている単純な構造なので、記事ごとに分割して、必要なタグの中身を取り出しています。本文は content:encoded、公開状態は wp:status、カスタムフィールドは wp:postmeta に入っています。

scripts/import-wordpress.mjs(抜粋)
const items = xml.split('<item>').slice(1).map((raw) => ({
  id: Number(field(raw, 'wp:post_id')),
  type: field(raw, 'wp:post_type'), // post / page / attachment / yyi_rinker など
  status: field(raw, 'wp:status'), // publish / draft など
  title: decodeEntities(field(raw, 'title')),
  name: safeDecode(field(raw, 'wp:post_name')), // スラッグ
  date: field(raw, 'wp:post_date'),
  modified: field(raw, 'wp:post_modified'),
  content: field(raw, 'content:encoded'),
  meta: metas(raw),
}));

wp:post_type が post のものが記事、page が固定ページ、attachment が画像です。下書きの記事は、移行後も下書き(draft: true)として残しました。

3. HTML を Markdown に変換する

HTML から Markdown への変換には Turndown を使いました。表は turndown-plugin-gfm で GitHub 形式の表に変換しています。

ただ、WordPress の本文の HTML をそのまま Turndown に渡すだけでは、うまくいきません。

クラシックエディタの記事は「段落タグがない」

ブロックエディタ(Gutenberg)以前のクラシックエディタで書いた記事は、本文に <p> タグが入っておらず、改行だけで段落を区切っています。WordPress は表示するときに wpautop という処理で改行を <p> や <br> に変換しているため、エクスポートした HTML をそのまま変換すると、段落がつながってしまいます。

そこで、WordPress 公式の @wordpress/autop パッケージで、WordPress と同じ処理をしてから変換しています。

ブロックエディタの記事は「コメント」に情報がある

ブロックエディタの記事には、<!-- wp:paragraph --> のような HTML コメントでブロックの情報が入っています。埋め込み(YouTube など)や再利用ブロック、テーマ独自のブロック(関連記事カードなど)は、このコメントの属性から内容を取り出して変換しました。

scripts/import-wordpress.mjs(抜粋)
// ブロックエディタの記事か、クラシックエディタの記事かで処理を分ける
html = /<!-- wp:/.test(html) ? convertBlocks(html, ctx) : autop(convertShortcodes(html, ctx));

4. テーマ・プラグイン独自の装飾を移す

このブログでは、テーマ(SANGO)のボックスや吹き出し、Rinker の商品リンクをたくさん使っていました。これらは WordPress の外では動かないので、Markdown で書ける独自の記法を用意して、変換時に置き換えています。

WordPress変換後の Markdown
SANGO のボックス:::point / :::memo / :::alert など
SANGO の吹き出し:::bubble{name="takeishi"}
SANGO のタイムライン:::steps
Rinker の商品リンク::product{id="..."}
関連記事カード::card{slug="..."}
YouTube の埋め込み::youtube{id="..."}

記法は remark-directive という Markdown の拡張に合わせていて、表示するときに独自のプラグインで HTML に変換しています。

Rinker の商品データ

Rinker の商品は yyi_rinker というカスタム投稿として XML に入っていて、商品名や画像 URL、ASIN、楽天の検索 URL などはカスタムフィールドに保存されています。これを 1 つの JSON ファイル(products.json)にまとめ、記事の中では ::product{id="4081"} のように ID だけを書く形にしました。

アフィリエイトの ID は表示するときに付けるようにしたので、ID が変わっても JSON や記事を書き換える必要はありません。

5. 画像をダウンロードする

本文やアイキャッチの画像は、記事ごとのフォルダ(src/content/blog/スラッグ/)にダウンロードしています。記事と画像が同じ場所にあると、あとから記事を修正するときに迷いません。

ダウンロードで気をつけたのは次の 3 点です。

  1. リサイズ版より元画像を優先する:WordPress は photo-300x200.jpg のような縮小版を自動で作るので、-300x200 を取り除いた元画像を先に試します。
  2. はてなブログ時代の画像も取得する:古い記事の画像ははてなフォトライフに置いてあったので、そちらからもダウンロードしました。
  3. 日本語ファイル名の濁点に注意する:Mac から上げたファイル名は、濁点が分解された形(NFD)になっていることがあります。NFC と NFD の両方の URL を試し、保存するファイル名は NFC にそろえました。

一度ダウンロードした画像はキャッシュしておき、変換スクリプトを何度実行し直しても、画像を取り直さないようにしています。

6. 内部リンクと旧 URL を整理する

このブログは、はてなブログ → WordPress と引っ越しを重ねてきたので、記事の中の内部リンクに古い形式の URL がたくさん残っていました。

  • はてなブログ時代の URL(/entry/2016/12/07/020555)
  • 途中でスラッグを変えた記事の古い URL

変換時に、これらを今の記事 URL(/entry/スラッグ/)に書き換えています。さらに、外部サイトや検索結果から古い URL でアクセスされたときのために、旧 URL → 新 URL の対応表を JSON で書き出し、Cloudflare Workers で 301 リダイレクトしています。

変換結果を確認する

変換が終わったら、記事の件数、ダウンロードできなかった画像、見つからなかった商品やリンクなどをレポート(JSON)に書き出して確認しました。失敗があれば原因ごとに変換処理を直して、実行し直す、を繰り返しています。

よくある質問

プラグインを使わずに、WordPress の記事を Markdown にできますか?

できます。管理画面の「ツール」→「エクスポート」で書き出した XML を、スクリプトで読み込んで変換します。HTML から Markdown への変換は Turndown などのライブラリが使えます。

変換後の記事の URL は変わりますか?

記事のフォルダ名を WordPress のスラッグと同じにすれば、URL は変わりません。このブログでは /entry/スラッグ/ という WordPress 時代の URL をそのまま使っています。

画像はどうやって移行しますか?

エクスポートの XML には画像の URL しか入っていないため、移行元のサイトが動いているうちに、URL から画像をダウンロードします。

まとめ

  • WordPress の記事は、エクスポートした XML(WXR)から Markdown に変換できる
  • クラシックエディタの記事は、wpautop と同じ処理をしてから変換する
  • テーマやプラグイン独自の装飾は、Markdown の独自記法に置き換える
  • 画像は早めにダウンロードし、日本語ファイル名の NFC / NFD に注意する

変換スクリプトを書くのは大変でしたが、一度 Markdown になってしまえば、記事は VS Code で書いて git push するだけで公開できます。

関連記事静的サイトにお問い合わせフォームを無料で付ける|Cloudflare Workers×Email Routing×Turnstile 関連記事静的サイトで人気記事ランキングを自動更新する|Cloudflare Web AnalyticsのAPI×GitHub Actions

Related関連記事