HtmlUnit+Jsoupで動的ページをスクレイピングしてみる

2020/08/09

とあるサイトからデータを拝借しようとしてjsoupでスクレイピングのコードを書いたのですが、うまく取得できません。

調べてみたらJavaScriptで画面を動的に組み立てる作りになっていました。そりゃ無理だ。jsoupは静的なHTMLにしか対応していないのです。

なんとかならないかなー?と思って調査したらHtmlUnitに辿り着きました。

HtmlUnitとは

HtmlUnitはJavaで書かれたヘッドレスブラウザです。Rhinoが組み込まれていてJavaScriptにも対応しています。

Unitという名前の通り、他のテスト用フレームワークと組み合わせてテストで使われることが多いみたい。2020年現在も開発が活発に続いているのが頼もしい。

試してみる

現時点の最新版は2.42.0。バイナリはSourceForge.netからダウンロードできるんですが、依存関係が結構あるライブラリなので取捨選択するのが面倒。GradleやApache Ivyあたりに任せてしまったほうがいいかもです。

URLを指定して結果を取得し、jsoupで解析するコードはこんな感じになります。

// ログの抑止
LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
java.util.logging.Logger.getLogger("com.gargoylesoftware.htmlunit").setLevel(Level.OFF);
java.util.logging.Logger.getLogger("org.apache.commons.httpclient").setLevel(Level.OFF);

// Chromeをシミュレートする
try (final WebClient webClient = new WebClient(BrowserVersion.CHROME)) {

  // Scriptでエラーが起きてもExceptionを投げないようにする
  webClient.getOptions().setThrowExceptionOnScriptError(false);

  HtmlPage page = webClient.getPage("https://example.com/");
  String html = page.asXml();
  Document document = Jsoup.parse(html);

  // jsoupで解析
}

page.asXml() でHTML(正確にはXML)のStringが取得できるので、これをJsoup.parse() で解析すればOK。

HtmlUnit単体でもDOMが扱えるみたいですが、凝ったことをするならjsoupと組み合わせたほうがラクなんじゃないでしょうか。

よく分からないこと

AJAXの制御がイマイチよく分かりません。ドキュメント読んでも釈然としないんだよなー。

公式サイトのFAQによると

  1. webClient.setAjaxController(new NicelyResynchronizingAjaxController()); を指定し、非同期XHRを再同期化する
  2. webClient.waitForBackgroundJavaScript() を使う
  3. webClient.waitForBackgroundJavaScriptStartingBefore() を使う
  4. JavaScriptの実行後に満たされるであろう条件が成立するのを明示的に待つ

を適宜使い分けてね、ってことのようなんですが、どう使い分ければいいんでしょうか。

そもそも 1 ~ 3 が具体的にどういう挙動になるのかはっきりしないんですよね。

4 は「JavaScript実行中は画面にLoading...という文言が表示され、実行が完了されたら消える」ページなら「Loading...が表示されている間は待つ」みたいなループを書け、ってことですよね。確かに確実だけど、どんなサイトでも都合よく条件が見つかるとは限りません。

幸か不幸か今回のターゲットとなったサイトはこれらを指定しなくても解析できてしまったので、掘り下げて調べるのは見送りに。

ご存知の方がいらっしゃいましたら、ご自身のblogかQiitaあたりに記事を書いてくれると嬉しいな……

おススメ?

確実性を考えるとNode.jsでPuppeteerを使うのが一番よさそう。PuppeteerはヘッドレスなChromeを呼び出すので、Chromeで動かしているのと同じ結果になるはず。

HtmlUnitは言ってしまえば「独自ブラウザ」なので、ChromeやFirefox、Safariといったメジャーブラウザとの挙動差はありえます。そもそもJavaScriptのエンジンがRhinoな時点でメジャーブラウザと違いますし。

Javaで書きたい!というときはHtmlUnitが第一選択肢になりそうですが、そうじゃなければ他の選択肢を検討したほうが無難かな、と思います。




おススメ