"Collective Intelligence"のサンプルを,はてなに対応させてみた

本の方では del.icio.us のブックマークを取得して,自分(じゃなくてもいいんだけど)と似ている人を探し,その人のリンクでまだ自分が登録していないものをお勧めするプログラムが出てくる。それを,はてなに対応させてみた。こっちもやや長いけど hatena_rec.rb。

require 'open-uri'
require 'rss'
require 'uri'

def init_dict(tag, count=5, users=30)
  result = Hash.new
  # 指定のキーワードのブックマーク一覧(rss形式 人気順/新着順)
#  sk = 'hot'  # 注目
#  sk = 'eid'  # 新着順
  sk = 'count' # 人気順
  rss_uri = "http://b.hatena.ne.jp/t/#{URI.encode(tag)}?mode=rss&sort=#{sk}&threshold=3"
  3.times do |i|
    begin
      open(rss_uri) do |rss_src|
        popular_rss = RSS::Parser.parse(rss_src)
        # そのurlをブックマークしているユーザの一覧(rss形式)
        popular_rss.items[0,count].each do |item|
          begin
            open("http://b.hatena.ne.jp/entry/rss/#{item.link}") do |src|
              # ブックマークしているユーザ一覧のrssは不正な形式(<items>の中に<rdf:Seq>が無い)なので
              # parseの第二パラメータにfalseを渡す
              rss = RSS::Parser.parse(src, false)
              rss.items[0,users].each do |item|
                # ここではユーザ毎に空ハッシュのみ用意する
                result[item.title] = Hash.new
              end
            end
          rescue
            puts "#{$!} : cannot get user list"
          end
        end
      end
      break
    rescue
      puts "#{$!} : cannot get popular bookmarks"
      sleep(5)
      next
    end
  end
  exit if result == {}
  result
end

def fill_items(dict, link_num=30)
  all_items = Array.new
  count = 0
  # ユーザによってポストされたリンクを全て取得する
  src = nil
  dict.keys.each do |user|
    count += 1
    3.times do |i|
      begin
        src = URI.parse("http://b.hatena.ne.jp/#{user}/rss").read
        break
      rescue
        puts "failed to get user's bookmark. retry(#{i+1})"
        sleep(5)
        next
      end
    end
    begin
      RSS::Parser.parse(src, false).items[0,link_num].each do |item|
        dict[user][item.link] = 1.0
        all_items << item.link
        puts "#{count} : #{item.link}"
      end
    rescue
      puts "rss parse err. skipping : http://b.hatena.ne.jp/#{user}/rss"
      next
    end
  end
  # なかったアイテムは0をセットする
  dict.values.each do |items_h|
    all_items.each do |url|
      items_h[url] ||= 0.0
    end
  end
end

if $0 == __FILE__ then
  require 'recommendation.rb'
  users = init_dict('rails')
  exit if users == {}
  users['ma2'] = Hash.new
  fill_items(users)
  puts '--- top_matches ---'
  top_matches(users,'ma2').each do |m|
    score = "%4.2f" % m[0]
    puts "#{score} : #{m[1]}"
  end
  recs = get_recommendations(users,'ma2')[0,10]
  if recs != []
    puts '--- get_recommendations ---'
    recs.each do |r|
      puts "#{r[0]} : #{r[1]}"
    end
  end
  puts '--- get_recommendations(xformed) ---'
  if recs != []
    url = get_recommendations(users,'ma2')[0][1]
    puts url
    top_matches(transform_prefs(users),url).each do |m|
      score = "%5.3f" % m[0]
      puts "#{score} : #{m[1]}"
    end
  end
end

最後の方のユーザ名(ma2)とタグ名(rails)を書き換えてください。
ちなみにこれをやってみると,このままではブックマークはユーザ間で全然重なっていないことが分かる。ブックマークを取得する方法を工夫する必要があると思う。top_matchesのスコアはかなり低い。ただお勧めはけっこういいところをついてる気がする。ちなみに get_recommendations(xformed) の方は,人間中心ではなくて,アイテム中心で計算した結果です。この場合だと,お勧め URL の最初の 1 つをとって,これに近似した別の URL を探しています。実行結果は以下の通り。

--- top_matches ---
0.02 : toenobu
0.02 : sukesam
0.02 : moro-tyo
0.02 : miya2000
0.02 : ftnk
--- get_recommendations ---
0.4 : http://itpro.nikkeibp.co.jp/article/Watcher/20070924/282781/
0.4 : http://d.hatena.ne.jp/amachang/20071010/1192012056
0.4 : http://code.nanigac.com/
0.2 : https://www.google.com/accounts/ServiceLogin?service=sitemaps&passive=true&nui=1&continue=http%3A%2F%2Fwww.google.com%2Fwebmasters%2Ftools%2Fsiteoverview&followup=http%3A%2F%2Fwww.google.com%2Fw
ebmasters%2Ftools%2Fsiteoverview&hl=en
0.2 : https://jinmyaku-bank.cafe.rikunabi.com/
0.2 : http://youmos.com/news/cooltips
0.2 : http://www1.doshisha.ac.jp/~mjin/R/e_corpusR.html
0.2 : http://www1.bbiq.jp/kougaku/ARToolKit.html
0.2 : http://www004.upp.so-net.ne.jp/s_honma/probability/bayes.htm
0.2 : http://www.zentus.com/sqlitejdbc/
--- get_recommendations(xformed) ---
http://itpro.nikkeibp.co.jp/article/Watcher/20070924/282781/
0.57 : https://www.google.com/accounts/ServiceLogin?service=sitemaps&passive=true&nui=1&continue=http%3A%2F%2Fwww.google.com%2Fwebmasters%2Ftools%2Fsiteoverview&followup=http%3A%2F%2Fwww.google.com%2F
webmasters%2Ftools%2Fsiteoverview&hl=en
0.57 : https://jinmyaku-bank.cafe.rikunabi.com/
0.57 : http://www1.doshisha.ac.jp/~mjin/R/e_corpusR.html
0.57 : http://www.technobahn.com/cgi-bin/news/read2?f=200709222351&page=2
0.57 : http://www.prezvision.com/