WordPressからMiyadaikuへの記事移行
2018-03-21

この記事では WordPress から Miyadaiku へ記事を持ってくるときにやったことを記録として残します。

Miyadaiku 自体のセットアップは Miyadaiku を始めよう を参照ください。

備考

  • この記事では 現時点での最新である 0.0.51 のバージョンを使います。

WordPressでの作業

警告

※WordPress のバージョンによっては UI が違うかもしれません

メニューからエクスポート画面に移動します。

menu.png

今回は すべての投稿を移動するだけなので 投稿 にチェックを入れ エクスポートファイルをダウンロード ボタンをクリック。

export.png

これで完了です。 wordpress.2018-02-27.xml がダウンロードされ、当該記事では このファイルを使うので、各自読み替えてください。

記事ファイルの一括生成

下準備

ダウンロードしたファイル を xml ライブラリ(ビルトイン)で読み出そうとすると、

>>> import xml.etree.ElementTree as ET
>>> tree = ET.parse('wordpress.2018-02-27.xml')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/xml/etree/ElementTree.py", line 1182, in parse
    tree.parse(source, parser)
  File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/xml/etree/ElementTree.py", line 594, in parse
    self._root = parser._parse_whole(source)
xml.etree.ElementTree.ParseError: unbound prefix: line 42, column 0

エラーがでました。

仕方がないので XML を直接書き換えます。

<atom:link rel="hub" href="http://pubsubhubbub.appspot.com"/>
<atom:link rel="hub" href="http://pubsubhubbub.superfeedr.com"/>

を消したらパースできました。

pubsubhubbub というプラグイン を導入していたために発生したと考えられます。 導入しているプラグインによっては別のエラーが発生する可能性があります。

変換

パースする準備が整ったので XML (内のHTML) を reST に変換していきます。

Miyadaiku 自体が HTML フォーマットの記事をサポートしていますし、 .. raw:: html を使うことで reST 内に HTML を書けるんですが、全部きれいな reST にしてやります。表示が崩れるのが怖かったのです。

というわけで pandoc を使い HTML から reST へ変換します。

インストール
  • $ brew install pandoc (Mac なら)
  • $ pip install python-dateutil pypandoc

XML の /channel/item が各記事で、 item から 情報を抜き出して reST を作成します。

title 記事のタイトル
pubDate 記事の投稿日
link 記事のURL
content:encoded

自分の場合、コードは以下のような感じになりました。

(定数が import より上に来てるけど突っ込まないでください。他の人が利用するときに書き換えやすいためです)

XML = 'wordpress.2018-02-27.xml'

META_TEMPLATE = '''
.. article::
  :date: {date}
  :title: {title}
  :canonical_url: {path}
  :tags:

'''

import io
import os
from urllib.parse import urlparse
from datetime import datetime
import xml.etree.ElementTree as ET

import pypandoc
import dateutil.parser as parser

tree = ET.parse(XML)
root = tree.getroot()
channel = root.find('channel')
for item in channel.findall('item'):
    meta = {
        'title': item.find('title').text,
        'date': parser.parse(item.find('pubDate').text).strftime('%Y-%m-%d'),
        'path': urlparse(item.find('link').text).path,
    }
    html = item.find('{http://purl.org/rss/1.0/modules/content/}encoded').text
    if html is None:
        continue
    try:
        os.makedirs(meta['path'].lstrip('/'))
    except OSError:
        pass

    input_path = 'temp.html'
    output_path = os.path.join(meta['path'].lstrip('/'), 'index.rst')
    with io.open(input_path, mode='w', encoding='utf-8') as f:
        f.write(html)

    rst = pypandoc.convert_file(input_path, 'rst')
    with io.open(output_path, mode='w', encoding='utf-8') as f:
        f.write(META_TEMPLATE.format(**meta) + rst)

警告

上記のコードを利用する場合

  • XML 変数は 自分が DL した xml ファイルのパスに書き換えてください。
  • 自分の場合、タグをすべてつけ直すつもりだったので tags はすべて空で出力しました。
    • 引き継ぎたい場合は、自力でどこかから抽出してください。

とりあえず出力できたようです。

$ ls -R .
2014                     2016                     temp.html
2015                     2017                     wordpress.2018-02-27.xml

./2014:
ansible-practice                           python-decorator
backup-sitefiles-to-bitbucket              python-logging
django-aggregate                           python-module
github-bitbucket-public-key-regist         vagrant-error
github-dotfiles                            windows7-window-transparent-off
google-2step-auth-setting                  wordpress-admin-common-ssl
microsoft-skype-unification                wordpress-image-insert-modify
public-key-basic-config                    wordpress-publish-confirm
pypi-debut                                 wordpress-revision-clean
python-argument-intro                      wordpress-syntax-highlighter
python-blogger-api                         wordpress-tinymce-disable-actual-reference

./2014/ansible-practice:
index.rst

./2014/backup-sitefiles-to-bitbucket:
index.rst

./2014/django-aggregate:
index.rst

./2014/github-bitbucket-public-key-regist:
index.rst

./2014/github-dotfiles:
index.rst

./2014/google-2step-auth-setting:
index.rst

./2014/microsoft-skype-unification:
index.rst

./2014/public-key-basic-config:
index.rst

./2014/pypi-debut:
index.rst

./2014/python-argument-intro:
index.rst

./2014/python-blogger-api:
index.rst

./2014/python-decorator:
index.rst

./2014/python-logging:
index.rst

./2014/python-module:
index.rst

./2014/vagrant-error:
index.rst

./2014/windows7-window-transparent-off:
index.rst

./2014/wordpress-admin-common-ssl:
index.rst

./2014/wordpress-image-insert-modify:
index.rst

./2014/wordpress-publish-confirm:
index.rst

./2014/wordpress-revision-clean:
index.rst

./2014/wordpress-syntax-highlighter:
index.rst

./2014/wordpress-tinymce-disable-actual-reference:
index.rst

./2015:
django-form                                 mikutter-install
django-genericview                          python-cannot-see-history-by-direction-keys
flets-ocn-diversion                         python-colander
get-aws-access-key                          python-mock
github-pull-request                         python-s3-boto
gnu-screen-study                            python-unixdomainsocket-exclusion-control
mariadb-install-with-ansible                rundeck-basic
mercurial-history-modification

./2015/django-form:
index.rst

./2015/django-genericview:
index.rst

./2015/flets-ocn-diversion:
index.rst

./2015/get-aws-access-key:
index.rst

./2015/github-pull-request:
index.rst

./2015/gnu-screen-study:
index.rst

./2015/mariadb-install-with-ansible:
index.rst

./2015/mercurial-history-modification:
index.rst

./2015/mikutter-install:
index.rst

./2015/python-cannot-see-history-by-direction-keys:
index.rst

./2015/python-colander:
index.rst

./2015/python-mock:
index.rst

./2015/python-s3-boto:
index.rst

./2015/python-unixdomainsocket-exclusion-control:
index.rst

./2015/rundeck-basic:
index.rst

./2016:
circieci-tox-pytest                     python-iterator-generator-and-tshirt-me
git-conflict                            python-metaclass
git-image                               python-pytest
git-rebase                              python-tox
mercurial-to-git                        python-tox-vagrant

./2016/circieci-tox-pytest:
index.rst

./2016/git-conflict:
index.rst

./2016/git-image:
index.rst

./2016/git-rebase:
index.rst

./2016/mercurial-to-git:
index.rst

./2016/python-iterator-generator-and-tshirt-me:
index.rst

./2016/python-metaclass:
index.rst

./2016/python-pytest:
index.rst

./2016/python-tox:
index.rst

./2016/python-tox-vagrant:
index.rst

./2017:
nekoatsume-data-migration    python-posix-ipc-systemv-ipc rundeck-docker
python-multiprocessing       python-scope                 rundeck-git
python-openpyxl-excel        recursive-function           ssh-tunnel

./2017/nekoatsume-data-migration:
index.rst

./2017/python-multiprocessing:
index.rst

./2017/python-openpyxl-excel:
index.rst

./2017/python-posix-ipc-systemv-ipc:
index.rst

./2017/python-scope:
index.rst

./2017/recursive-function:
index.rst

./2017/rundeck-docker:
index.rst

./2017/rundeck-git:
index.rst

./2017/ssh-tunnel:
index.rst

画像ダウンロード

このままだと画像とかが WordPress にあるものを参照してるので 手元に持ってきて src も書き換えます。

URL = 'http://note.crohaco.net'

import re
import os
from urllib import request, parse

for d, _, fs in os.walk('.'):
    for f in fs:
        if not f.endswith('.rst'):
            continue
        rst = os.path.join(d, f)
        txt = open(rst, encoding='utf-8').read()
        for img in reversed(list(re.finditer(r'image:: ({}.*)'.format(URL), txt))):
            url = img.group(1)
            image_name = os.path.basename(parse.urlparse(url).path)
            image_path = os.path.join(d, image_name)
            res = request.urlopen(url)
            open(image_path, 'wb').write(res.read())
            txt = txt[:img.start()] + 'image:: ' + image_name + txt[img.end():]
        open(rst, 'w', encoding='utf-8').write(txt)

警告

上記のコードを利用する場合 URL を自分のブログのドメインに書き換えてください。

これで reST ファイル中の画像がローカルにDLしたものに差し替わりました。

仕上げは手動で..

で、このディレクトリたちを contents/ に移動すれば概ね完了なんですが ..

docutils.utils.SystemMessage: /root/cronote/contents/2016/python-pytest/index.rst:565: (SEVERE/4) Unexpected section title.

やっぱり死にました。

どうやらブログ内に多用しまくってた table タグ内コードの変換がうまく行かずエラーになってしまったようです。

+-----------------------------------+-----------------------------------+
| test_skipif.py                    | py.test test_skipif.py            |
+===================================+===================================+
| ::                                | ::                                |
|                                   |                                   |
|     # coding: utf-8               |     test_skipif.py sF             |
|     from unittest import TestCase |                                   |
|     import pytest                 |     ============================= |
|                                   | = FAILURES ====================== |
|                                   | ========                          |

list-table に変換されてほしかったんですが table で出力してくれましたね..

一旦テーブル内のコードをすべて消して保存。 これでとりあえず記事のビルドが通りました。

テーブル構造部分は手動調整でがんばるしかなさそうです。鞍替えする人は一緒に頑張りましょう。(記事が多いと一日じゃ終わらないかもね..