ScalaでWebスクレイピングして画像収集する
2011年も相変わらず「Scalaは実用的なのか?」という
「え、実用的も何も、普通に使ってますが、、、」としか答えられない質問を幾度も受けました。
Scalaは実用的で、例えばコミケのコスプレ画像の収集などができます。*1
【コミケ81】コスプレイヤー画像まとめ:1日目【C81】さとろぐ。
からjpg画像を一括ダウンロードし
"data/(画像のURLの最後の/以下)"というファイル名で保存しようと思います。
ポイント
- dispatchでHTMLを取得する
- LiftのHTMLパーサでHTMLをxmlに変換する
- ScalaのXMLサポートで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とは違い、ScalaはXMLを(中途半端に)便利に扱えるので
やってもらいたいことは、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するんだと思っておけば良いでしょう。
ScalaのXMLサポートで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
がとても詳しいので丸なげしようと思います。
ScalaのXMLサポートはいろいろとビミョーなとこがあるのですが、どこがダメなのかがよくわかります!
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すればいいかんじです。