summer_tree_home

Check iOでPython3をマスターするぜっ

Python3.3で、はてなブログAtomPubを使ってみた

はてなブログの不満の一つが、エクスポート機能が無いということ。
他社のブログに移行したいわけじゃないが、そのうち引っ越しを考えるかもしれないし、そもそもWebサービスは無保証なんだから、PCにバックアップぐらいしておきたい。

幸い、AtomPubという機能を使えば、ブログ記事を取得したり更新したりできるので、Python3.3を使って、ブログの全記事をダウンロードしてみた。

AtomPubの説明はこちら。
はてなブログAtomPub - Hatena Developer Center

やりたいこと

  1. AtomPubを使って、自分のブログの全記事をダウンロードしたい。
  2. (できれば)他の形式に変換したい。

AtomPubでは、記事をXML形式でダウンロードすることになるので、他社のブログに移行するのなら、他の形式に変換する必要があるだろう。
今回は、とりあえず1のダウンロードまでやってみる。

開発環境は、Python3.3.3 (32bit) + Windows7
必要なモジュール:

  • requests
  • BeautifulSoup4

追記

別の環境で試したところ、エラーが出てうまくいかなかったが、lxml を入れることで解決した。
Windowsなら、下記サイトからバイナリ版をダウンロードしてインストールするのが簡単だ。
http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml

AtomPubを使う準備

はてなID、ブログID、APIキーの3つが必要。
ブログIDはブログのアドレスで、このブログなら "py3.hateblo.jp" となる。
APIキーはブログの設定>詳細設定に記載されている。

ブログ記事のリストを取得するには、コレクションURIを GET すればよい。

コレクションURI https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry

認証とダウンロード

requests で GET

import requests

r = requests.get(url)

普通のサイトなら、これだけでダウンロードできるのだが、今回は認証が必要。
はてなのAtomPubでは、Basic認証、WSSE認証、OAuth認証 の3種類に対応している。

Basic認証

ユーザ名=はてなID、パスワード=APIキー となる。

import requests
from requests.auth import HTTPBasicAuth

r = requests.get(url, auth=HTTPBasicAuth(hatena_id, api_key))

これだけ。簡単だ。

WSSE認証

X-WSSEヘッダを付けてGETする。こちらも、ユーザ名=はてなID、パスワード=APIキー。

import requests
import datetime, random, hashlib, base64

def create_wsse(username, password):
    """ X-WSSEヘッダの値を作成
    """
    created = datetime.datetime.now().isoformat() + "Z"
    b_nonce = hashlib.sha1(str(random.random()).encode()).digest()
    b_digest = hashlib.sha1(b_nonce + created.encode() + password.encode()).digest()
    s = 'UsernameToken Username="{0}", PasswordDigest="{1}", Nonce="{2}", Created="{3}"'
    return s.format(username, base64.b64encode(b_digest).decode(), base64.b64encode(b_nonce).decode(), created)

r = requests.get(url, headers={'X-WSSE': create_wsse(hatena_id, api_key)})

WSSE認証は下記サイトを参考にした。
はてなブログのAPIが公開されたらしいので簡単に触ってみた(Python) - 唯物是真 @Scaled_Wurm
そのままではPython3で動かなかったため、少し変更している。

OAuth認証

まずアプリケーションを登録?なんか、ややこしそうなのでパス。
WSSE認証から OAuth への移行 - Hatena Developer Center

記事の続きをダウンロード

ここまでで、最新記事10件分が含まれたXMLをダウンロードできた。
このXMLの中に、記事の続き(11件目以降)をダウンロードするためのURIが書いてある。

<link rel="next" href="https://(略)/atom/entry?page=1377575606" />

このhref="~"の部分を取り出して、GET する。これを繰り返す。

BeautifulSoup

XMLを解析するためにBeautifulSoupを使う。
BeautifulSoupは、HTMLやXMLを解析するクラスで、XMLを使うときにはコンストラクタの引数に features='xml' を付ければいい。
<link ref="next" href="URI"> というタグを探して、href=の値を取得してみる。

from bs4 import BeautifulSoup

soup = BeautifulSoup(r.content, features='xml')
tag = soup.find('link', rel='next')
url = tag.get('href') if tag else None

これで、続きをダウンロードするためのURIが取得できる。取得できなくなったら、終了。

まとめてみた

import requests
from requests.auth import HTTPBasicAuth
from bs4 import BeautifulSoup

def download_xml_files():
    hatena_id = '(はてなID)'
    blog_id = '(ブログID)'
    api_key = '(APIキー)'

    url = 'https://blog.hatena.ne.jp/{}/{}/atom/entry'.format(hatena_id, blog_id)

    for i in range(1, 100):
        # XMLをダウンロード
        print('GET', url)
        r = requests.get(url, auth=HTTPBasicAuth(hatena_id, api_key))
        if r.status_code != 200:
            break

        # XMLを保存 (blog_001.xml)
        path = 'blog_{0:03d}.xml'.format(i)
        with open(path, 'wb') as f:
            f.write(r.content)

        # 次のURIを取得
        soup = BeautifulSoup(r.content, features='xml')
        tag = soup.find('link', rel='next')
        url = tag.get('href') if tag else None

        if url is None:
            break

実行すると、カレントフォルダに blog_001.xml ~ のファイル名で保存される。
私の場合、記事数が少ないので、3ファイルだけだった。
(ちなみに画像などは含まれていない。文章だけだ。)

後日、このXMLをもう少し見やすい形式に変換しようと思う。


続きの記事をアップした