ゴミ箱の中のメモ帳

まだ見ぬ息子たちへ綴る手記

はてなブログ エクスポートスクリプト in Python

はてなブログにはなぜかエクスポート機能が実装されていない。

私がこれを知ったのは最近のことだが、検索するとこれに対する不満が多いようだ。エクスポート機能がないという事はバックアップができないという事で、不慮の事故や誤操作に保険をかけておくことができない。個人のブログなら一つの思い出として済むかも知れないが、法人や団体が使っている場合はこのエクスポート(バックアップ)機能は必須になるかと思う。

図書館支援プログラム」なんかを見るとそう言った団体にもはてなブログを広げていきたいようだが、エクスポートができないというのはブログを選択する際に減点対象になると考えられる。私も今までにいくつものサイトにブログを提供してきたが、管理側でバックアップが取れないものは考慮外としてきた。


と言う事で、「はてなブログ AtomPub APIのPython WSSEサンプルコード」、「はてなブログ AtomPub コレクションXML フォーマット仕様」記事からの通りはてなブログをエクスポートできるスクリプトを書いてみた。

初めてのPython 第3版

初めてのPython 第3版


エクスポートスクリプトは最下部に貼りつけるが、このスクリプトには欠点がある。スクリプトというか、はてなブログ自体がエクスポートを前提にしていないため現在には難が一つある。

下のスクリプトを実行すると簡易的なMT形式でファイルを出力するようにしている。だがそのMT形式内の文章はブログの記入モードのままで書きだすようにしてある。と言う事は、「はてな記法モード」で書かれている場合は「はてな記法モード」でエクスポートする。HTML形式でもエクスポート出来るのだが、HTML形式でエクスポートすると今後の編集が大変になるため意図的にこのようにしてある。

だが、はてなブログのインポート機能を使うとすべて「見たままモード」としてインポートされてしまう。これは「はてな記法モード」か「Markdownモード」のみで起こる現象になるが、現在のはてなブログではこれを使ってエクスポート、インポートをする際には避けることのできない問題になってしまう。


この問題を解決する方法としては、インポート機能を使わずにAtomPub APIを利用して全ての記事を投稿する方法と、インポートした後に全ての記事の記述モードを変更する方法の、2つがあるがどちらも別にスクリプトを実装する必要があるためプログラムを書けない人には敷居が高くなってしまう。

実際にこのスクリプト自体もPythonの実行環境やプログラムの実行経験がなければ敷居の高いものになるかと思う。そう思うので、自分のために作ったといえ「バックアップできない」事に不便を考えている人達のことを考え、はてなブログのバックアップ実行サービスを作ろうと思う。これは簡単に実装できるため近日中に製作しようかと思う。


但し注意して欲しいのは、もし同じようなサービスが展開されたとしても「絶対」に不用意に使ってはいけない。APIKeyを伝えるというのは「パスワード」を教えるのと同じ意味になるため、ブログに関する全ての権限を与えてしまうのと同じ事になる。よってブログに勝手に投稿することも出来れば全ての記事を削除することすら出来てしまう。「バックアップ」するつもりが「バックアップ前に全て削除される」という結果になってしまうので絶対に不用意に「API Key」を伝えてはならない。これは私に対しても同じ事だ。


また、このエクスポートサービスはWebサーバの帯域が必要なために少々コストがかかってしまう。もし応援してくださる方がいれば「Amazonほしい物リスト」か「Amazonギフト券」で捻出しようかと思う。提供いただける方がいればコメント頂きたい。。


以下がはてなブログのエクスポートスクリプト。使い方は「はてなブログ AtomPub APIのPython WSSEサンプルコード」と同様になる。

#!/usr/bin/python

import datetime
import random
import hashlib
import base64
import urllib2
from xml.dom import minidom

userid = "UserID"
apikey = "API Key"
blogid = "Blog ID"
apiurl = "http://blog.hatena.ne.jp/%s/%s/atom/entry" % ( userid, blogid)



def make_wsse( userid, apikey):
    created_at = datetime.datetime.now().isoformat() + "Z"
    nonce = hashlib.sha1( str( random.random())).digest()
    digest = hashlib.sha1( nonce + created_at + apikey).digest()

    return 'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"' % ( userid, base64.b64encode( digest), base64.b64encode( nonce), created_at)


headers = { "X-WSSE": make_wsse( userid, apikey)}

entries = []

url = apiurl
end = False
while True:
    request = urllib2.Request( url, headers=headers)
    response = urllib2.urlopen( request)
    dom = minidom.parseString( response.read())
    links = dom.getElementsByTagName( "link")
    for link in links:
        if link.attributes["rel"].value == "next":
            url = link.attributes["href"].value
            break
    else:
        end = True

    for entry in dom.getElementsByTagName( "entry"):
        mt_entry = {}
        mt_entry["category"] = []
        for node in entry.childNodes:
            if node.nodeType == node.ELEMENT_NODE:
                if node.tagName == "author":
                    mt_entry["author"] = node.firstChild.firstChild.data
                elif node.tagName == "title":
                    mt_entry["title"] = node.firstChild.data
                elif node.tagName == "updated":
                    mt_entry["date"] = datetime.datetime.strptime( node.firstChild.data[:19], "%Y-%m-%dT%H:%M:%S").strftime( "%m/%d/%Y %H:%M:%S")
                elif node.tagName == "summary":
                    mt_entry["excerpt"] = node.firstChild.data
                elif node.tagName == "content":
                    mt_entry["body"] = node.firstChild.data
                elif node.tagName == "category":
                    mt_entry["category"].append( node.attributes["term"].value)
                elif node.tagName == "app:control":
                    if node.getElementsByTagName("app:draft")[0].firstChild.data == "no":
                        mt_entry["status"] = "Publish"
                    else:
                        mt_entry["status"] = "Draft"

        entries.append( mt_entry)
    if end:
        break

for entry in entries:
    print """
TITLE: %s
AUTHOR: %s
DATE: %s
STATUS: %s
%s
-----
BODY:
%s
-----
EXCERPT:
%s
-----
--------
""" % ( entry["title"], entry["author"], entry["date"], entry["status"], "\nCATEGORY: ".join( ["",]+entry["category"]), entry["body"], entry["excerpt"])

Pythonスタートブック

Pythonスタートブック

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理

パーフェクトPython (PERFECT SERIES 5)

パーフェクトPython (PERFECT SERIES 5)

エキスパートPythonプログラミング

エキスパートPythonプログラミング