ScalaでWebスクレイピングして画像収集する

2011年も相変わらず「Scalaは実用的なのか?」という
「え、実用的も何も、普通に使ってますが、、、」としか答えられない質問を幾度も受けました。
Scalaは実用的で、例えばコミケのコスプレ画像の収集などができます。*1


【コミケ81】コスプレイヤー画像まとめ:1日目【C81】さとろぐ。
からjpg画像を一括ダウンロードし
"data/(画像のURLの最後の/以下)"というファイル名で保存しようと思います。


ポイント

  • dispatchでHTMLを取得する
  • LiftのHTMLパーサでHTMLをxmlに変換する
  • ScalaXMLサポートでxmlを解析して画像のURLを抜き出す
  • ExtractorでURLの分解
  • scala-ioを使ってファイルに保存

dispatchでHTMLを取得する

別にdispatchでなくてもscala標準のscala.io.sourceでもできるし、scalaj.httpとかJavaのHttpClientでもなんでもできますが、dispatchを使いたかったのでdispatchです。

    val h = new Http
    val req = url("http://satlog.blog119.fc2.com/blog-entry-2943.html") >\ "EUC-JP"
    val html: String = h(req as_str)


Scalaに不慣れな人は「>\ってなんだよ...」と思うのではないでしょうか。
Scalaに慣れてても「>\ってなんだよ...」ってなるので気にすることはないです。
dispatchはこの他にも変な演算子がいっぱいあります。
ソースコードもかなり読みづらいです!
でも便利です!
そのためやたらクオリティの高いチートシートがあります。


LiftのHTMLパーサでHTMLをxmlに変換する

Javaとは違い、ScalaXMLを(中途半端に)便利に扱えるので
やってもらいたいことは、HTML→XMLの変換くらいです。
LiftのHtmlパーサなら簡単にできます。

    val elem: NodeSeq = Html5.parse(html) openOr NodeSeq.Empty


Html5.parseにhtml文字列を渡せばBox[Elem]が返ってきます。
BoxはOptionみたいなもので、LiftではこれがOptionの代わりに使われています。
Optionとの違いですが、
OptionがSomeとNoneなのに対して
BoxはFull, Empty, Failure, ParamFailureの4種類があります。
Lift使わない人はとりあえずOptionみたいなやつで、getの代わりにopen_!、getOrElseの代わりにopenOrするんだと思っておけば良いでしょう。

ScalaXMLサポートでxmlを解析して画像のURLを抜き出す

    def jpgFilter(filename: NodeSeq): Boolean = filename.text endsWith ".jpg"

    // <img>タグのsrcのURLを抜き出し、jpgのものだけ選んで保存する。
    elem \\ "img" \\ "@src" filter jpgFilter foreach { img => save(img.text) }

XMLリテラルなどの使いかたは
Working with Scala’s XML Support - Code Commit
がとても詳しいので丸なげしようと思います。
ScalaXMLサポートはいろいろとビミョーなとこがあるのですが、どこがダメなのかがよくわかります!

ExtractorでURLの分解

URLの最後のスラッシュ以下を抜き出すなにかが欲しいです。
def extractFilenameFromURL(url: String): String
とかしても良いですが、せっかくだからパターンマッチしましょう。

object作ってunapply定義するとパターンマッチに使えるExtractorができるんだぜ。

object Filename {
  // URLの最後のスラッシュ以下を抜き出す
  def unapply(url: String): Option[String] = {
    url.split("/").reverse.toList.headOption
  }
}

こうやって使います。

    url match {
      case Filename(file) => file
      case _ => sys.error("Oops!")
    }

Extractorの使いかたについてはunfilteredのソースを読みましょう。

scala-ioを使ってファイルに保存

Scala IO API Documentation
標準のIOではなく「incubatorのIO」と呼ばれているものです。
Scalaでnew BufferedInputStream...とか書きたくないぽよ〜」って人にオススメなライブラリです。

  def save(url: String): Unit = {
    val data = Resource.fromURL(url).byteArray
    url match {
      case Filename(file) => Resource.fromFile(new java.io.File("data", file)).write(data)
      case _ => sys.error("Oops!")
    }
  }


ざっくり言うとResource.fromFileとかfromURLとかやってあとはread/writeすればいいかんじです。

まとめ

というわけで、ScalaでWebスクレイピング、簡単ですね。*2


みなさんよいお年を。


最後にまるっとgist貼っておきます。


*1:エロ画像とかでも良いがここからエロサイトにリンクを貼るのは避けたい

*2:でも画像一括ダウンロードとかはぶっちゃけScalaじゃなくてFIrefoxのアドオン入れりゃいいとは思う