はてなブックマークのAtom Feedの仕様について

big32005-12-05

こういう報告はどこにすればいいんだろうか? (報告の必要があるのかどうかもわからないんだが)

今、簡単なPythonスクリプトを書いて、はてなブックマークAtomAPIにアクセスし、ブックマークを取り出してみた。

そしたら途中でエラーになった。(途中で!!)

俺はAtomAPIのことも、RESTも、Unicodeも、はてなブックマークの仕様もよく理解していないので、以下おかしなことが書いてあるかもしれないが、一応、俺の理解した範囲で症状を書いてみる。

まず、エラーになったfeedをメモ帳で開いてみた。すると日本語が全て文字化けしていた。

次に、勘を働かせて、取り出すfeedの開始位置を少しずらしてみた。その結果、エラーになったブックマークエントリーを特定することができた。これだ。↓

http://b.hatena.ne.jp/entry/http://support.microsoft.com/kb/909444

このエントリーが入ってるfeedは、UTF-8的に不正なデータが入っているので、メモ帳も「なーんかこれUTF-8じゃーねーなー」ってことで、全部文字化けし、Pythonも、こんな↓エラーを吐いたと推測。(推測)

xml.parsers.expat.ExpatError: not well-formed (invalid token): line 30, column 26

バイナリエディタでエラーになったfeedを開いてみてみると、エントリーのタイトルは全部で255バイトあった。(255!) 最後の2バイトは EF BF だった。UTF-8エンコードの仕方は忘れてしまったが、この部分がおかしいのかな?? (よくみるとタイトルの最後が四角のハテナになっている)

なにがおかしいのかよくわからないけど、(俺がおかしいのかな? Expat? メモ帳?) 255バイトでタイトルを切るとき、もし、文字の途中で切れてしまうのなら、その一文字分を全部削っておかないと、UTF-8的に不正な文字をfeedで出してしまうってことじゃないかと推測。

折角なので、使ったPythonスクリプトを以下に掲載する。こちらのページこちらのページを参考にさせてもらって(ありがとう!!)、ほとんどそのまま写して使わせてもらっている。(あ、Pythonも、はじめてまもないです)

from urllib import *
from xml.dom.minidom import *
from time import *
from random import *
from datetime import *
import urllib2, base64, sha, re

def feedretrieve(user, passwd, uri, filename):
    dt = datetime

    nonce = str(random()) + str(dt.today())
    created = dt.now().isoformat() + 'Z'
    d = sha.new(nonce + created + passwd)

    wsseheader = 'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", 
Created="%s"' % (user, base64.encodestring(d.digest()).strip(), 
base64.encodestring(nonce).strip(), created)

    req = urllib2.Request(uri)
    req.add_header('X-WSSE', wsseheader)
    req.add_header('Content-Type', 'text/xml')

    file(filename, 'w').write(urllib2.urlopen(req).read())

seed()

i = 0

user = 'username_here'
passwd = 'your_passwd_here'
baseuri = 'http://b.hatena.ne.jp/atom/feed'
uri = baseuri

path = 'c:/somewhere'
hasNext = True

while hasNext:
    filename = path + str(i) + '.xml'
    #urlretrieve(url, filename)
    feedretrieve(user, passwd, uri, filename)
    print uri, " -> ", filename
    xml = parse(filename)
    root = xml.documentElement
    url = ''
    for node in root.childNodes:
        if node.nodeName == 'link' and node.getAttribute('rel') == 'next':
            url = node.getAttribute('href')
            break
    if url[:4] == 'http':
        hasNext = True
        m = re.search(r'\?.*', url)
        if m:
            uri = baseuri + m.group(0)
    else:
        hasNext = False
    sleep(randint(5,9))
    i += 1

追記: タイトル末尾だけじゃなくて、途中に文字化け(?)があってもエラーになることがわかった。たとえばこれ↓。俺のスクリプトのほうでワンタッチで文字化けを除去できるといいんだけどな。

http://b.hatena.ne.jp/entry/http://www.asahi.com/sports/update/1204/107.html