BeautifulSoupを使って2つのはてなブックマークで共通するユーザのコメントを抽出してみる

Togetterの抽出ネタでも良かったんですが、まずは身近な方で。


BeautifulSouppythonのHTML/XMLパーサライブラリで、多少の文法誤りを許容・修正してくれる上に非常に使いやすいおすすめライブラリです。
はてなAPIがあるんでXMLで叩けばいいんですが、今回は練習も含めてHTMLでやってみました。
まず、はてなブックマークのコメント一覧のHTMLを分析します。

  • 各ユーザは一人毎にli要素にまとめられている。
    • li要素は属性"id"及び"data-user"を持つ
      • "id"には"bookmark-user-[ユーザID]"という値を持つ
      • "data-user"には"[ユーザID]"がそのまま値となる
    • コメントはli要素内、属性class="comment"のspan要素で書かれている

このことを踏まえ、はてブのコメントリストページからIDとコメントを抽出し、IDをキーとした辞書に変換する関数を作ります。

import urllib2
from BeautifulSoup import BeautifulSoup
import re

def get_comments(url):
    soup = BeautifulSoup(urllib2.urlopen(url))
    commentdict = {}
    for user in soup.findAll("li", id=re.compile("bookmark-user")):
        commentdict[user["data-user"]] = ''.join(user.find("span", "comment").findAll(text=True))
    return commentdict

何これ超簡単。
pythonらしくないところはヘタレCプログラマの限界と思って下さい。ジェネレータとリスト内包で一時辞書を作らずそのまま返すほうがかっこいいかも。
基本的なところはすっ飛ばして、BeautifulSoup周りのみ解説。

  • findAllメソッドは対象HTML全体から指定した要素のみを抜き出し、リスト化する関数。上記の通り正規表現も使えます。
  • findAll等で得られた各要素のインスタンスは、属性名を指定すればその属性に設定された値をそのまま得られます。上ではli要素のdata-user属性値(ユーザID)をそのまま辞書のキーとして使用。
  • findAll(text=True)で対象インスタンス以下の全ての値をリストとして得られます。"".joinで繋げれば全部の地の文字列をそのまま抜けます。


あとは二つのURLからそれぞれ上記関数で辞書を作り、重複するIDのみ抽出すればOK。

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 3:
        print "usage: %s [bookmark url 1] [bookmark url 2]" % sys.argv[0]
        sys.exit(1)
    dict_pre = get_comments(sys.argv[1])
    dict_suf = get_comments(sys.argv[2])
    for user in set(dict_pre).intersection(dict_suf):
        print "User : %s" % user
        print "Comment pre : %s" % dict_pre[user]
        print "Comment suf : %s" % dict_suf[user]

二つの辞書の交差(重複)キーを抽出するのにsetを使っています。インスタンス生成を減らすため、各辞書毎にsetインスタンスを作成せず、一つだけ生成したsetインスタンスのintersectionメソッドを使用。これはPython Cookbook 4.17に記載されてます。


あとは二つを繋げて完成。python素敵。
これで今日から貴方もダブスタチェッカー!