WordPressからHugoへ:全投稿をMarkdownにエクスポートする (2)
前回はWordPressからエクスポートしたXMLファイルをPythonで処理して、Hugoの記事に必要なFrontmatterを書き出すところまでできました。 今回は本文の処理を行ってみましょう。
最初に注意事項なのですが、前回同様に今回の内容もわたしのブログに特化した処理をしていますので、他の環境でうまくいく保証はできません。
また今回は特に味付けというか、好みが反映される部分ですので、みなさんのブログの本文にあわせて調整してみてください。
エクスポートしたファイル中の記事本文
前回のおさらいですが、WordPressから出力したXMLファイル中には <item> というタグで囲まれた部分に記事とメディアの情報がそれぞれ格納されています。
余計な要素を消すと記事本文は、次のような場所に入っていることになります。
<item>
<content:encoded><![CDATA[<!-- wp:paragraph -->
<p>本文</p>
</content:encoded>
<wp:post_type><![CDATA[post]]></wp:post_type>
︙
ですから、すべての記事について本文だけを取り出すコードは以下のようになります。
import xml.etree.ElementTree as et
tree = et.parse('all-posts.xml')
root = tree.getroot()
# get each attachment and post
featured_img = {}
for i in root.iterfind('./channel//item',ns):
type = i.find('.//wp:post_type',ns).text
if (type == 'post'):
content = i.find('.//content:encoded',ns).text
困ったことに、古いWordPress記事は段落が開業で分割されているのに対して、最近のGutenbergに対応したものはコメント欄で制御が行われているところです。たとえば古い記事本文は簡単で以下のようになっています。
<h2>Inbox に放り込む</h2>
「車検の予約を入れる」、「メールの返事」、「旅行の計画をたてる」、「ホームページの更新」、「整理整頓」、「新しい本を読む」、などなど。頭のなかから取り出した全てのアクションは、小さなものから大きなものまで含めると少なくとも百近い「やること」のリストになっているはずです。
こうしたものはすべて Inbox と呼ばれる受け皿にとりあえず保管しておきます。一枚の紙でもいいですし、コンピューターや Palm 上の ToDo でもかまいません
。とりあえず頭のなかからとりだしておくことが重要なのです。
ところが最近ものはこんなに複雑です。
<!-- wp:paragraph -->
<p>私は Freedom の手軽さと、複数デバイスの同期機能が好きで有料で利用しているのですが、ちょうど2020年の春である本稿の執筆時点で40%オフのセールが開
催中です。永久に利用可能な Forever プランも6000円ほどになっているので、どうせ何年も使いそうだというひとは思い切ってこのプランでいってもいいと思い
ます。</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2>SNSから適度に離れる</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>コロナウィルスの影響が社会に広まるにつれて、SNS上でもさかんに情報がやりとりされるだけでなく、不安や怒りや苛立ちといったものも目立つようになり始めています。</p>
<!-- /wp:paragraph -->
「面倒だな…」と思ったので、乱暴ですが記事本文の HTML コメントはすべて削除するようにしました(もっといい方法があったら教えてください)。
content = re.sub("(<!--.*?-->)", "", content)
Markdownify で本文をMarkdownに変える。ただ、すこし修正を
何種類か試してみたのですが、私は Python でテキストをMarkdownに変換するのにMarkdownifyを使用しました。Markdownifyのインストールは、
pip install markdownify
で簡単に終わります。ただ、そのまま使用すると今回の記事本にとって困った部分がありましたので、ソースコードに手を入れて無理やり解決しました。1つ目は、古いWordPress記事のように、段落が空行で分けてある場合に、それが無視されて連続した文章になってしまう点です。
def process_text(self, text):
return escape(whitespace_re.sub(' ', text or ''))
↓ こちらに修正
def process_text(self, text):
return
これは process_text というメソッドでホワイトスペースなどをエスケープしている行がありましたので、乱暴ですがこれを無視するように修正。
def convert_img(self, el, text):
alt = el.attrs.get('alt', None) or ''
src = el.attrs.get('src', None) or ''
title = el.attrs.get('title', None) or ''
title_part = ' "%s"' % title.replace('"', r'\"') if title else ''
return '![%s](%s%s)' % (alt, src,title_part)
↓ こちらに修正
return '![%s](%s)' % (alt, src)
もう一箇所は単に好みなのですが、画像にタイトルを入れる形式のMarkdownが入っているとあとで画像の一括処理が面倒だったので、タイトルが入らないようにしておきました。時間がないので本当に乱暴…。
Markdownify のソースコードは Python のインストールディレクトリの lib/python3.7/site-packages/markdownify のなかに init.py というファイルで存在したりしますので、そこを編集してください。もちろん、コメントも入れておいてあとで戻せるようにしておいてください。
注意したいのは、今回のこの処理の場合、基本的に HTML のタグは通しません。アフィリエイトタグ、ツイッターの埋め込みなどといったものはゴミのようにテキストの中に残ってしまうようになります。このあたりはタグのホワイトリスト化、ブラックリスト化で対応が可能ですので、Markdownify のドキュメントを参考にして調整してください。
わたしは過去のアフィリエイトタグなどといったものはあとですべてHugoのShortcodeに置き換えようと思っていましたので、このようにしています。
from markdownify import markdownify as md
content = md(content, heading_style="ATX")
あとは、一行でテキストをMarkdownに変換できます。heading_style=ATX は、見出しを Setext 形式ではなく、Atx 形式で出力せよという意味です。たとえば h2 の見出しを ## で始まる行で書く方式のことですね。
さて、これで本文もできましたので、あとはこれをファイルに書き出していくだけです。ただし過去の資産をうまく整理したいので、一歩踏み込んだことをしてみます。それについては次回。