Rubyで書くはてブを使った推薦
書籍の例では,del.icio.usからソーシャルブックマークのデータを取得して推薦を行っています.(p.20-p.24を参照)
これをこのままRubyで書こうかな...とも思ったのですが,何か面白くないので
はてなブックマーク(以下 はてブ)のデータを取ってくることにします.
はてブのデータを取得する
はてブのデータはRSSで配信されているので,全てRSS経由でデータを取ってくることにします.
RSSライブラリを使えばパースも楽ですし.
なお,任意のID(下の例ではkj-ki)のブックマークデータも強制的にマージできるようにしました.
これで被推薦者を決め打ちできます.
require 'rss' module My class HatenaBookmark def initialize(tag, how_many, options = {}) @hot_entries = "http://b.hatena.ne.jp/t/#{URI.escape(tag)}?mode=rss&sort=hot" @how_many = how_many @my_hatena_id = options[:my_hatena_id] end # # mainループ # ホットエントリ取得からID取得、BookmarkされているURL取得まで一気に行う # def make_users_and_bookmarks hot_urls = get_hot_urls users = get_all_bookmarked_users(hot_urls) # 指定したブックマークデータも収集対象にする users << @my_hatena_id if @my_hatena_id users_and_bookmarks = {} users.each do |user| bookmarked_urls = get_bookmarked_urls(user) users_and_bookmarks[user] = critics_of(bookmarked_urls) end pad_all_urls_into_all_users(users_and_bookmarks) end # # ホットエントリのURLを抽出する # def get_hot_urls rss = RSS::Parser.parse(@hot_entries) rss.items.map { |item| item.link }.slice(0...@how_many) end # # 複数URLでブックマークしているIDを全て抽出する # def get_all_bookmarked_users(urls) urls.map { |url| get_bookmarked_users(url) }.flatten.uniq end # # エントリをブックマークしているIDを抽出する # def get_bookmarked_users(url) entry = "http://b.hatena.ne.jp/entry/rss/#{url}" # falseを入れないとパース時にエラーになってしまう rss = RSS::Parser.parse(entry, false) rss.items.map { |item| item.title } end # # 任意のIDがブックマークしているURLを抽出する # ページング処理を加味して最大150件まで # def get_bookmarked_urls(user) offsets = [0, 30, 60, 90, 120] offsets.map { |offset| get_offset_bookmarked_urls(user, offset) }.flatten.compact end # # 指定したオフセットのブックマークしているURLを抽出する # RSSがWellFormedでない場合があるので、その時はnilを返す # offsetが大きすぎるとMissingTagErrorになるので、その時もnilを返す # def get_offset_bookmarked_urls(user, offset) bookmark = "http://b.hatena.ne.jp/#{user}/rss?of=#{offset}" begin rss = RSS::Parser.parse(bookmark) rescue RSS::NotWellFormedError, RSS::MissingTagError return nil end rss.items.map { |item| item.link } end # # URLのリストを評価値1.0としたハッシュに変換する # def critics_of(urls) url_and_critic = urls.map { |url| [url, 1.0] }.flatten Hash[*url_and_critic] end # # 各IDに対して、ブックマークしていないURLを0点で登録する # def pad_all_urls_into_all_users(users_and_bookmarks) users = users_and_bookmarks.keys all_urls = users.map { |user| users_and_bookmarks[user].keys }.flatten.uniq users.each do |user| (all_urls - users_and_bookmarks[user].keys).each do |url| users_and_bookmarks[user][url] = 0.0 end end users_and_bookmarks end end end
実行結果
今回は「はてな」タグが付加されているエントリを10個選んで,少なくともどれか1つをブックマークしているユーザのデータを取ってきます.
後で使いやすいように,一旦ファイルに落とします.
hatebu = My::HatenaBookmark.new('はてな', 10, { :my_hatena_id => 'kj-ki' }) File.open('hatena.dump', 'w') do |file| Marshal.dump(hatebu.make_users_and_bookmarks, file) end
いざ,実行!
% ./hatena_bookmark.rb % ls -la hatena.dump -rw-r--r-- 1 user staff 37013296 8 26 00:12 hatena.dump
時間は掛かりますが,そこそこの大きさのファイル(35MBくらい)ができました.
推薦してもらいましょう
では,このデータを使って推薦させてみましょう.
require 'fast_recommender' hatena_critics = Marshal.load(File.open('hatena.dump')) recommender = My::FastRecommender.new puts '----------top_matches' pp recommender.top_matches(critics, 'kj-ki', { :how_many => 10 }) puts '----------get_recommendations' pp recommender.get_recommendations(critics, 'kj-ki').slice(0...10)
いざ,実行!
% ./hatena_bookmark.rb ----------top_matches [[0.0201632277399705, "uva"], [0.020112244545605, "a_tsu_shi"], [0.0121706163873039, "Kirito"], [0.0116497107279458, "zackle"], [0.0116497107279458, "yorihito_tanaka"], [0.0116497107279458, "rinou"], [0.0116497107279458, "juniper"], [0.0116497107279458, "fk_2000"], [0.00470556061444605, "kisiritooru"], [0.00337173183496513, "lamich"]] ----------get_recommendations [["http://d.hatena.ne.jp/shibata616/20080825/1219627264", 0.455297428020195], ["http://q.hatena.ne.jp/tanakahideo/questionlist?page=1", 0.371765774979091], ["http://news.livedoor.com/article/detail/3790598/", 0.332950525317097], ["http://www.ibm.com/developerworks/jp/linux/library/l-10sysadtips/?ca=drs-jp", 0.262391110294427], ["http://www.net.c.dendai.ac.jp/~takumi/", 0.24428021683129], ["http://d.hatena.ne.jp/KZR/20080808/p1", 0.196793332720821], ["http://d.hatena.ne.jp/asami81/20080821/masuda", 0.189161546206615], ["http://www.itmedia.co.jp/news/articles/0808/19/news051.html", 0.179899305187593], ["http://itpro.nikkeibp.co.jp/article/COLUMN/20080417/299353/", 0.178682439257683], ["http://www.j-cast.com/tv/2008/08/19025331.html", 0.169956516751988]]
という訳で,get_recommendationsがkj-kiに推薦してくれたURL第1位は,
「増田さんへ。久しぶりにグレーターみのもんたが現れました - ls@usada’s Backyard」でした!
これだけやってみのもんた...