Scalaで集合知プログラミング その7


Scala de 集合知プログラミングの第2章の続きをやっていきますよ(`・ω・´)

今回はdel.icio.usAPIを使って集合知的ゴニョゴニョをしていきますよ

del.icio.usのリンクを推薦するシステムを作る

前回までにやったアイテムの推薦、という内容を実際のサービスを利用したものとして発展させていきたいと思います。

今回サービスとして利用するのはオンラインブックマークのdel.icio.usデス

delicious

...と、本が出版されてからサービス名が地味に変わっている気がするのですが、まあ(゚ε゚)キニシナイ!!方向で

del.icio.usAPI

とりあえずDeli.cio.usをPythonで利用しやすくするためにpydeliciousを導入してやります
pydelicious google code

導入はeasy installであっさりと

$sudo easy_install pydelicious

それでは使ってみますかね(`・ω・´)

>>> import pydelicious
# 注目されているprogrammingタグのついたブックマーク一覧を取得しました
>>> pydelicious.get_popular(tag='programming')
[{'extended': '', 'description': u'Using the Cython Compiler to write fast Python code', 'tags': u'python programming c compiler performance c++ optimization software presentation cython', 'url': u'http://www.behnel.de/cython200910/talk.html', 'user': u'remosu', 'dt': u'2009-10-30T13:32:03Z'}, {'extended': '', 'description': u'ontwik | Lectures, Screencasts and conferences for real web developers & designers', 'tags': u'screencast conferences webdev webdesign video resources programming education learning development', 'url': u'http://ontwik.com/', 'user': u'certainly', 'dt': u'2010-10-16T14:47:50Z'}, {'extended': '', 'description': u'Free ebook: Programming Windows Phone 7, by Charles Petzold', 'tags': u'free silverlight programming phone windows windowsphone7 microsoft development mobile pdf', 'url': u'http://blogs.msdn.com/b/microsoft_press/archive/2010/10/28/free-ebook-programming-windows-phone-7-by-charles-petzold.aspx', 'user': u'derek.lakin', 'dt': u'2010-10-28T16:43:38Z'}, {'extended': '', 'description': u'What Every Computer Scientist Should Know About Floating-Point Arithmetic', 'tags': u'programming math', 'url': u'http://docs.sun.com/source/806-3568/ncg_goldberg.html', 'user': u'agles', 'dt': u'2004-03-25T14:44:49Z'}, {'extended': '', 'description': u"Making Asynchronous Programming Easy - Somasegar's WebLog - Site Home - MSDN Blogs", 'tags': u'programming asynchronous c# vb.net', 'url': u'http://blogs.msdn.com/b/somasegar/archive/2010/10/28/making-asynchronous-programming-easy.aspx', 'user': u'andyparkhill', 'dt': u'2010-10-28T22:35:48Z'}, {'extended': '', 'description': u'Ksplice \xbb Hosting backdoors in hardware - System administration and software blog', 'tags': u'security kernel hardware linux hacking programming drivers unix', 'url': u'http://blog.ksplice.com/2010/10/hosting-backdoors-in-hardware/', 'user': u'handshandy', 'dt': u'2010-10-27T17:35:55Z'}, {'extended': '', 'description': u'IMPURE', 'tags': u'data information interaction interface programming software technology visualization tool', 'url': u'http://www.impure.com/', 'user': u'creoquecreo', 'dt': u'2010-10-26T10:02:32Z'}, {'extended': '', 'description': u'Phono - jQuery Phone Plugin', 'tags': u'jquery phone programming webdesign', 'url': u'http://www.phono.com/', 'user': u'socialpest', 'dt': u'2005-03-03T10:23:43Z'}]

出版後しばらく立っているのでAPI廃止されてたらどうしよう…と思ってたのですが、何とか使えるみたいですね(´・ω・`)

ただし、仕様変更はあったらしくどうやら取得件数制限がかかるようになってますね(´・ω・`)デフォルトだと15件までみたい…って、負荷を考えればそりゃそうだわな

ちなみに取得用urlにパラメータをcount=<件数>で指定すると最大100件までは取れるみたいですが…pydeliciousで今回使用するメソッドにはそういうオプションがない…だ..と...orz

うーん、大規模なデータが取得できないとなると余り分析に意味は出なそうなのですが…まあ、今回はコードを書くのが第一目的なのでとりあえずやってみマス

Scalaにはモジュールがない…よ..な、そりゃ

…と、Pythonでは本書の指示通りにAPIを使える見込みが立ったのですが、本特集の本題であるScalaでやるという目的を達成できるような、Scala de deliciousなモジュールは提供されていなさそう(´・ω・`)

いや、あるのかも知れないけど正直探すのが大変すぐるので、今回のサンプルの中で利用しているpydeliciousのメソッドを車輪の再発明してみますよ

  • get_popular
  • get_userposts
  • geturlposts
get_popularメソッド

まずは最新のpopularな投稿リストを取得するメソッドです

呼び出しはこんな感じですね

pydelicious.get_popular(tag='programming')

タグを引数として渡すことで、指定タグの付けられたもののみを取得します

pydelicious.get_popularで実際に取得できるデータはこんな感じですね

# 全部表示するとあれなので2件だけ表示します
>>> pydelicious.get_popular(tag='programming')[0:2]
# 出力データです
>>> pydelicious.get_popular(tag='programming')[0:2]
[{'extended': '', 'description': u'Using the Cython Compiler to write fast Python code', 'tags': u'python programming c compiler performance c++ optimization software presentation cython', 'url': u'http://www.behnel.de/cython200910/talk.html', 'user': u'remosu', 'dt': u'2009-10-30T13:32:03Z'}, {'extended': '', 'description': u'ontwik | Lectures, Screencasts and conferences for real web developers & designers', 'tags': u'screencast conferences webdev webdesign video resources programming education learning development', 'url': u'http://ontwik.com/', 'user': u'certainly', 'dt': u'2010-10-16T14:47:50Z'}]

上記の結果を見ると、戻りはこんな感じのキーで構成されたMapデータで返ってくれば良いみたいですね

  • extended
  • description
  • tags
  • url
  • user
  • dt

また、pydeliciousのコードを眺めてみると、内部的には以下のURLに接続してRSS情報を引っ張るような処理をしているみたいデス

http://delicious.com/rss/popular/<タグ名>

programmingタグがつくとたとえばこんな感じ
http://delicious.com/rss/popular/programming


そんなわけでURLからフィードを取得して、適当に要素を再構成するコードを書いてみました(´・ω・`)

import scala.io.Source
import scala.xml.{XML, NodeSeq}
import scala.xml.parsing.XhtmlParser

// 取得用のURLを定義します
val rss_url = "http://delicious.com/rss/"

// popularなフィードを取得するメソッドです
def get_popular(tag:String = ""):List[Map[String, String]] = {
  // URLから提供フィードを取得してXMLの"item"要素を取得します
  val source = Source.fromURL(rss_url + "popular/"+ tag)
  val feeds = XhtmlParser(source) \\ "item"
  // 取得した各フィードの内容を再構成します
  feeds.map(feed =>
    // extendedについては該当のものが見当たらないので
    // 適当に空白扱ってます(´・ω・`)まあ、今回は使わないので…
    // 残りの要素は多分これでいいはずデス
    Map("extended" -> (feed \\ "extended").text,
      "description" -> (feed \\ "title").text,
      "tags" -> (feed \\ "subject").text,
      "url" -> (feed \\ "link").text,
      "user" -> (feed \\ "creator").text,
      "dt" -> (feed \\ "date").text
    )
  ).toList
}

参考にしたのはこちら

get_userpostsメソッド

これは与えられたユーザのポストを取得するメソッドです

呼び出しはこんな感じですね

pydelicious.get_userposts(user='tsegaran')

ユーザ名を引数として渡してやります(`・ω・´)ちなみにサンプルの"tsegaran"は集合知プログラミングの筆者さんデス

pydelicious.get_userpostsで実際に取得できるデータはこんな感じですね

# 全部表示するとあれなので2件だけ表示します
>>> pydelicious.get_userposts(user='tsegaran')[0:2]
# 出力データです
[{'extended': '', 'description': u'How to Build a 8\xd78 RGB LED Matrix with PWM using an Arduino | Francis ...', 'tags': u'arduino shiftregister', 'url': u'http://francisshanahan.com/index.php/2009/how-to-build-a-8x8x3-led-matrix-with-pwm-using-an-arduino/', 'user': u'tsegaran', 'dt': u'2010-09-09T00:19:54Z'}, {'extended': '', 'description': u'shogun | A Large Scale Machine Learning Toolbox', 'tags': u'python svm machinelearning', 'url': u'http://www.shogun-toolbox.org/', 'user': u'tsegaran', 'dt': u'2010-07-15T23:09:37Z'}]

こちらのほうも戻りはこんな感じのキーで構成されてればいいみたいデス(´・ω・`)

  • extended
  • description
  • tags
  • url
  • user
  • dt

pydeliciousのコードからでは、以下のURLに接続してRSS情報を引っ張ってますね

http://delicious.com/rss/<ユーザ名>

tsegaranのフィードを引っ張る場合はこんな感じ
http://delicious.com/rss/popular/tsegaran

以上の情報からこちらの方もScala版を作成しますデス

// 取得用のURLを定義します
val rss_url = "http://delicious.com/rss/"

// userのフィードを取得するメソッドです
def get_userposts(user:String):List[Map[String, String]] = {
  // URLから提供フィードを取得してXMLの"item"要素を取得します
  val source = Source.fromURL(rss_url + user)
  val feeds = XhtmlParser(source) \\ "item"
  // 取得した各フィードの内容を再構成します
  feeds.map(feed =>
    Map("extended" -> (feed \\ "extended").text,
      "description" -> (feed \\ "title").text,
      "tags" -> (feed \\ "subject").text,
      "url" -> (feed \\ "link").text,
      "user" -> (feed \\ "creator").text,
      "dt" -> (feed \\ "date").text
    )
  ).toList
}
get_urlpostsメソッド

最後に与えられたURLに関する投稿を取得するget_urlpostsメソッドです

呼び出しはこんな感じですね

pydelicious.get_urlposts(url='http://google.co.jp/')

urlを引数として渡してやります

pydelicious.get_urlpostsで実際に取得できるデータはこんな感じですね

# 全部表示するとあれなので2件だけ表示します
>>> pydelicious.get_urlposts(url='http://google.co.jp/')[0:2]
# 出力データです
[{'extended': '', 'description': u'[from jayce_fun] Google', 'tags': u'google', 'url': u'http://www.google.co.jp/', 'user': u'jayce_fun', 'dt': u'2011-01-16T06:03:38Z'}, {'extended': '', 'description': u'[from largeleft] Google', 'tags': u'google', 'url': u'http://www.google.co.jp/', 'user': u'largeleft', 'dt': u'2011-01-14T13:56:33Z'}]

こちらのほうも戻りはこんな感じのキーで構成されてればいいみたいデス(´・ω・`)

  • extended
  • description
  • tags
  • url
  • user
  • dt

pydeliciousのコードからでは、以下のURLに接続してRSS情報を引っ張ってますね

http://delicious.com/rss/url/

http://google.co.jp/のフィードを引っ張る場合はこんな感じ
http://delicious.com/rss/url/ec16dada27c84a7c23fff9c27265355b

以上の情報からこちらの方もScala版を作成しますデス

import java.security.MessageDigest

// 取得用のURLを定義します
val rss_url = "http://delicious.com/rss/"

// urlのフィードを取得するメソッドです
def get_urlposts(url:String):List[Map[String, String]] = {
  // 引数として渡されたurlをハッシュ化します
  val hash_url = MessageDigest.getInstance("MD5").
    digest(url.getBytes()).map((n : Byte) => "%02x".format(n & 0xff)).mkString

  // URLから提供フィードを取得してXMLの"item"要素を取得します
  val source = Source.fromURL(rss_url + "url/"+ hash_url)
  val feeds = XhtmlParser(source) \\ "item"
  // 取得した各フィードの内容を再構成します
  feeds.map(feed =>
    Map("extended" -> (feed \\ "extended").text,
      "description" -> (feed \\ "title").text,
      "tags" -> (feed \\ "subject").text,
      "url" -> (feed \\ "link").text,
      "user" -> (feed \\ "creator").text,
      "dt" -> (feed \\ "date").text
    )
  ).toList
}

md5によるハッシュ化の参考はこちらデス
みずぴー日記

オブジェクト化

3つの各処理とも結構同じような処理をしているので共通化して、かつpydeliciousと同じように使用できるようにオブジェクトとしてまとめてしまいます

// パッケージ化します
package org.plasticscafe.collective.recommend

import scala.io.Source
import scala.xml.{XML, NodeSeq}
import scala.xml.parsing.XhtmlParser

object ScalaDelicious {
  // 取得用のURLを定義します
  val rss_url = "http://delicious.com/rss/"

  // popularなフィードを取得するメソッドです
  def get_popular(tag:String = ""):List[Map[String, String]] = {
    // urlからフィードを取得します
    get_feed(rss_url + "popular/"+ tag) 
  }
  
  // userのフィードを取得するメソッドです
  def get_userposts(user:String):List[Map[String, String]] = {
    get_feed(rss_url + user) 
  }
  
  // urlのフィードを取得するメソッドです
  def get_urlposts(url:String):List[Map[String, String]] = {
    // 引数として渡されたurlをハッシュ化します
    val hash_url = MessageDigest.getInstance("MD5").
      digest(url.getBytes()).map((n : Byte) => "%02x".format(n & 0xff)).mkString
      get_feed(rss_url + "url/"+ hash_url) 
  }
  
    // フィードを取得する共通処理です
  private def get_feed(url:String):List[Map[String, String]] = {
    // URLから提供フィードを取得してXMLの"item"要素を取得します
    val source = Source.fromURL(url)
    val feeds = XhtmlParser(source) \\ "item"
    // 取得した各フィードの内容を再構成します
    feeds.map(feed =>
      Map("extended" -> (feed \\ "extended").text,
        "description" -> (feed \\ "title").text,
        "tags" -> (feed \\ "subject").text,
        "url" -> (feed \\ "link").text,
        "user" -> (feed \\ "creator").text,
        "dt" -> (feed \\ "date").text
      )
    ).toList
  }
}

うん、なんとかまとまったみたいなので試しに動かしてみますよ(`・ω・´)

// インポートします
scala> import org.plasticscafe.collective.recommend.ScalaDelicious
import org.plasticscafe.collective.recommend.ScalaDelicious

// get_popularを使ってみます
scala> ScalaDelicious.get_popular("programming")                  
res2: List[Map[String,String]] = List(Map((extended,), (dt,2009-10-30T13:32:03Z), (url,http://www.behnel.de/cython200910/talk.html), (description,Using the Cython Compiler to write fast Python code), (tags,python programming c compiler performance c++ optimization software presentation cython), (user,remosu)), Map((extended,), (dt,2010-10-16T14:47:50Z), (url,http://ontwik.com/), (description,ontwik | Lectures, Screencasts and conferences for real web developers & designers), (tags,screencast conferences webdev webdesign video resources programming education learning development), (user,certainly)), Map((extended,), (dt,2010-10-28T16:43:38Z), (url,http://blogs.msdn.com/b/microsoft_press/archive/2010/10/28/free-ebook-programming-windows-phone-7-by-charles-petzold.aspx), (description,Free ...

// get_userpostsを使ってみます
scala> ScalaDelicious.get_userposts("tsegaran")                   
res3: List[Map[String,String]] = List(Map((extended,), (dt,2010-09-09T00:19:54Z), (url,http://francisshanahan.com/index.php/2009/how-to-build-a-8x8x3-led-matrix-with-pwm-using-an-arduino/), (description,How to Build a 8×8 RGB LED Matrix with PWM using an Arduino | Francis ...), (tags,arduino shiftregister), (user,tsegaran)), Map((extended,), (dt,2010-07-15T23:09:37Z), (url,http://www.shogun-toolbox.org/), (description,shogun | A Large Scale Machine Learning Toolbox), (tags,python svm machinelearning), (user,tsegaran)), Map((extended,), (dt,2010-07-15T23:08:55Z), (url,http://mypoyozo.com/#tour), (description,Poyozo | Make Life Make Sense), (tags,data personal visualization), (user,tsegaran)), Map((extended,), (dt,2010-07-08T21:43:23Z), (url,http://www.sagenb.org/), (description,Sign in -...

// get_urlpostsを使ってみます
scala> ScalaDelicious.get_urlposts("http://google.co.jp/")
res4: List[Map[String,String]] = List(Map((extended,), (dt,2011-01-16T06:03:38Z), (url,http://www.google.co.jp/), (description,[from jayce_fun] Google), (tags,google), (user,jayce_fun)), Map((extended,), (dt,2011-01-14T13:56:33Z), (url,http://www.google.co.jp/), (description,[from largeleft] Google), (tags,google), (user,largeleft)), Map((extended,), (dt,2011-01-14T02:51:19Z), (url,http://www.google.co.jp/), (description,[from ttake2000] Google), (tags,google), (user,ttake2000)), Map((extended,), (dt,2011-01-12T00:08:33Z), (url,http://google.co.jp/), (description,[from nakapond] Google), (tags,), (user,nakapond)), Map((extended,), (dt,2011-01-10T22:20:55Z), (url,http://www.google.co.jp/), (description,[from jospefois_2sc372] Google), (tags,), (user,jospefois_2sc372)), Map((extended,), (...

おお!できました(`・ω・´)

今回のまとめ

とりあえずScala用のpydelicious簡易版ができたので、次回はこいつを使ってdeli.cio.usのデータを操作して集合知的ゴニョゴニョをやりたいと思います(´・ω・`)