WordPressの記事をMarkdownに変換する|エクスポートしたXMLから画像・商品リンクまで移行した方法
目次
このブログを WordPress から静的サイト(Astro)に移行するにあたって、一番の大仕事が記事の変換でした。
10年分・200本以上の記事を、WordPress の HTML から Markdown ファイルに変換しています。変換スクリプトは Node.js で書き、何度も実行し直しながら調整しました。
1. WordPress からエクスポートする
WordPress の管理画面の「ツール」→「エクスポート」で「すべてのコンテンツ」を選ぶと、XML ファイル(WXR 形式)がダウンロードできます。
この XML には、記事や固定ページだけでなく、次のようなデータも入っています。
| データ | 中身 |
|---|---|
| 記事・固定ページ | タイトル、本文の HTML、公開日、更新日、スラッグ、公開状態 |
| カテゴリ・タグ | 名前とスラッグ、親子関係 |
| メディア | アップロードした画像の URL |
| カスタム投稿 | Rinker の商品データ、再利用ブロックなど |
| カスタムフィールド | アイキャッチ画像の ID、閲覧数(プラグインのデータ)など |
2. XML を読み込む
WXR は 1 記事が 1 つの <item> になっている単純な構造なので、記事ごとに分割して、必要なタグの中身を取り出しています。本文は content:encoded、公開状態は wp:status、カスタムフィールドは wp:postmeta に入っています。
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 など)や再利用ブロック、テーマ独自のブロック(関連記事カードなど)は、このコメントの属性から内容を取り出して変換しました。
// ブロックエディタの記事か、クラシックエディタの記事かで処理を分ける
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 点です。
- リサイズ版より元画像を優先する:WordPress は
photo-300x200.jpgのような縮小版を自動で作るので、-300x200を取り除いた元画像を先に試します。 - はてなブログ時代の画像も取得する:古い記事の画像ははてなフォトライフに置いてあったので、そちらからもダウンロードしました。
- 日本語ファイル名の濁点に注意する: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 するだけで公開できます。