CLOVER🍀

That was when it all began.

Dispatch × jsoup

先ほど使ってみたDispatchですが、core以外にもいくつかモジュールがあるようで、今回はその中でも興味のあったjsoupとの連携を試してみます。

dispatch-jsoup
https://github.com/dispatch/reboot/tree/master/jsoup

jsoup自体は、Javaで利用可能なHTML5向けパーサです。

jsoup: Java HTML Parser
http://jsoup.org/

このサイトでも、The Validator.nu HTML Parserと合わせて紹介させていただきました。

Javaで使える、HTML5パーサ
http://d.hatena.ne.jp/Kazuhira/20140107/1389108413

jQueryのようなCSSセレクタで要素を選択できたりするので、便利です。

で、Dispatchのjsoup向けのモジュールを利用するには、以下の様に定義します。

libraryDependencies ++= Seq(
  "net.databinder.dispatch" %% "dispatch-jsoup" % "0.11.0",
  "org.scalatest" %% "scalatest" % "2.0" % "test"
)

ScalaTestは、テストコード用です。

そして、dispatch-jsoupを使うテストコードを用意します。
src/test/scala/org/littlewings/dispatch/jsoup/DispatchJsoupSpec.scala

package org.littlewings.dispatch.jsoup

import scala.collection.JavaConverters._

import dispatch._
import dispatch.Defaults._

import org.scalatest.FunSpec
import org.scalatest.Matchers._

class DispatchJsoupSpec extends FunSpec {
  describe("dispatch jsoup spec") {
    // ここに、テストコードを書く!!
  }
}

まずは、jsoupのDocumentを取得してみましょう。対象は、Dispatchのオフィシャルサイトのトップページとします。ここからDocumentを取得して、コンテンツの中身を確認してみましょう。

    it("get document") {
      val request = host("dispatch.databinder.net") / "Dispatch.html"
      val document = Http(request OK as.jsoup.Document).apply()

      document.text() should include ("Dispatch is a library for asynchronous HTTP interaction.")

      // タグを指定して取得
      val h3Texts = document.getElementsByTag("h3").asScala.map(_.text().init)
      h3Texts should have size (4)
      h3Texts should contain theSameElementsInOrderAs Array("Diving in",
                                                            "Defining requests",
                                                            "Deferring action",
                                                            "Demanding answers")
    }

as.jsoup.Document(正確には、dispatch.as.jsoup.Document)というResponseHandlerを使用することで、Dispatchを使用したHTTPリクエストの結果が、jsoupのDocumentとなります。

とはいえ、as.jsoup.Documentの中身は、いたって簡単ですが。
https://github.com/dispatch/reboot/blob/master/jsoup/src/main/scala/as/soup.scala

object Document extends (Response => nodes.Document) {
  def apply(r: Response) =
    Jsoup.parse(dispatch.as.String(r), r.getUri().toString)
}

あとは、jsoupのAPIに沿ってプログラムを書いていけばOKです。

ところで、妙なところにinitが入っているのは、コンテンツ中にちょっと表示できなさそうな文字が入り込んでいるからです…。

      val h3Texts = document.getElementsByTag("h3").asScala.map(_.text().init)

最後にもうひとつ、Queryというのもご紹介します。

Queryを使用すると、いきなりjsoupのセレクタを渡すことができます。

    it("query#1") {
      val request = host("dispatch.databinder.net") / "Dispatch.html"
      // Queryでタグを指定して取得
      val h3Tags = Http(request OK as.jsoup.Query("h3")).apply()

      val h3Texts = h3Tags.asScala.map(_.text().init)
      h3Texts should have size (4)
      h3Texts should contain theSameElementsInOrderAs Array("Diving in",
                                                            "Defining requests",
                                                            "Deferring action",
                                                            "Demanding answers")
    }

    it("query#2") {
      val request = host("dispatch.databinder.net") / "Dispatch.html"
      // Queryでidを指定して取得
      val idDispatch = Http(request OK as.jsoup.Query("#Dispatch")).apply()

      idDispatch.text().init should be ("Dispatch")
    }

このQueryもやはり、DispatchのResponseHandlerとして実装されています。

as.jsoup.Documentと同じく、定義はめちゃくちゃ簡単です。
https://github.com/dispatch/reboot/blob/master/jsoup/src/main/scala/as/soup.scala

object Query {
  import org.jsoup.select.Elements
  def apply(query: String): Response => Elements =
    Document(_).select(query)
}

ちょっとしたショートカットというところですね。こういうの見てると、いろいろとResponseHandlerが実装できそうな気がしますね。Async Http Clientも知らなくちゃという気もしますが。

その他、json4s-jacksonなどもあるので、機会があれば試してみます。