はてなブックマークのお気に入りを新着の古い順に並べたい実装 リブート
kenjiro-n.hateblo.jp
という前回の記事を書いたのが約2年前だというのにも驚きましたが、その間にあったはてなブックマークのリニューアルで http://b.hatena.ne.jp/kenjiro_n/favorite という形式のURLは無効になり http://b.hatena.ne.jp/kenjiro_n/follow という形式になりました。
しかし初期表示で50件を表示しているこの画面をNokogiriでパースしてもユーザーの一覧は取得できずどうしたものかと思っていたところ、画面をスクロールさせるとデバッグコンソールのネットワークタブで
http://b.hatena.ne.jp/api/internal/user/kenjiro_n/followings?limit=51&of=50
なるURLにアクセスしていることがわかりました。Chromeだとcurl のコマンドライン形式をそのまま吐いてくれるのは本当に助かります。見たところこれははてな内部のAPIを受けるURLなのでこうやって公表すべきなのかどうかはちょっと悩みました。拡散力の弱い当ブログなのでとりあえず何も考えず見たままを書いておきますが、この内部API用URLへのアクセスについては筆者は責任を負わないものとすることを宣言します。
で、このAPIにアクセスするとお気に入りの一覧にあるはてなID等をJSON形式で取得できるので、それを拾い上げて各人のRSSの新着日時と組み合わせた一覧を雑なhtml形式としてファイル出力するところまで作りました。本当であれば頭の方に myhatenaid なる変数で設定している自分のはてなIDをコマンドライン引数で受け取り有効無効を見るところまで書きたくもあるのですが、所詮は自分のためだけのプログラムなのでそのあたりの実装は行いませんでした。
お気に入りリストのAPIのパラメータ引数が500を指定したにもかかわらず460件程度しか受け取れないなど挙動が怪しい点もありますが、これでとりあえずこの件は一旦終了とします。
# encoding: utf-8 # URLにアクセスするためのライブラリの読み込み require 'open-uri' require 'rss/1.0' require 'rss/dublincore' require 'json' myhatenaid = 'kenjiro_n' outFile = 'users.html' # スクレイピング先のURL # curl "http://b.hatena.ne.jp/api/internal/user/kenjiro_n/followings?limit=500" -H "Cookie: b=^$1^$mWg8dg6S^$MsNlhyrosnoBlgHqtIybi.; _ga=GA1.4.1083259439.1554864612; _gid=GA1.4.1142241765.1554864612; _dc_gtm_UA-20092244-16=1; __utma=12101991.1083259439.1554864612.1554864612.1554864612.1; __utmb=12101991.0.10.1554864612; __utmc=12101991; __utmz=12101991.1554864612.1.1.utmcsr=(direct)^|utmccn=(direct)^|utmcmd=(none); __gads=ID=98daa95633db2e44:T=1554864612:S=ALNI_MaIqszcoFWSNsw0x3bSBBwPXWR34w" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: ja,en-US;q=0.9,en;q=0.8" -H "User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Mobile Safari/537.36" -H "content-type: application/json" -H "accept: application/json" -H "Referer: http://b.hatena.ne.jp/kenjiro_n/follow" -H "x-requested-with: XMLHttpRequest" -H "Connection: keep-alive" --compressed -o list.xml url = 'http://b.hatena.ne.jp/api/internal/user/'+ myhatenaid + '/followings?limit=500' opt = {} opt['User-Agent'] = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/530.5 (KHTML, like Gecko) Chrome/2.0.172.33 Safari/530.5' resp = URI.open(url, opt) do |f| charset = f.charset # 文字種別を取得 f.read # htmlを読み込んで変数respに渡す end # 出力用のハッシュ users = {} # respをパース(解析)してオブジェクトを生成 # cf. https://qiita.com/yertea/items/be6f535fc31d7325ed97 doc = JSON.parse(resp) doc["followings"].each do |node| # ユーザーIDを取得 fr = node["name"] fr_uri = 'http://b.hatena.ne.jp/' + fr + '/rss' p fr URI.open(fr_uri, opt) do |httpr| response = httpr.read result = RSS::Parser.parse(response, false) result.items.each_with_index do |item,i| #puts item.title users[fr] = item.dc_date #puts fr + "\t" + item.dc_date.to_s + " break # 最新のアイテムだけ取得する end end end # ソートの値は戻さないといけなかった! users = Hash[ users.sort_by{ |_, v| v } ] # ごく基本的なhtml のファイルとして一覧を出力する File.open(outFile, "w") do |f| f.puts "<ul>" users.each{|key, value| dispname = "" doc["followings"].each do |node| # 表示名を取得 if key == node["name"] dispname = node["display_name"] break end end f.puts "<li>" f.puts "<a href=\"http://b.hatena.ne.jp/" + key + "/\">" + key + " (" + dispname + ")</a>" f.puts " (<a href=\"http://profile.hatena.ne.jp/" + key + "/\">profile</a>) lu:" + value.to_s f.puts "</li>" } f.puts "</ul>" end