Webオートメーションツールを使って、快適Webスクレイピング

皆さんこんにちは。しうへいと申します。
今回はWebオートメーションという技術と、それによって可能になる一歩進んだWebスクレイピングについて書きます。

##目次
* [Webオートメーションとは?](#anchor1)
* [Webオートメーションを可能にするプロダクト](#anchor2)
 * [PhantomJS](#anchor3)
 * [Selenium](#anchor4)
 * [Ui4j](#anchor5)
* [Ui4jの使用例](#anchor6)
* [Webオートメーションツールを使うメリット](#anchor7)
* [Ui4jの使用例](#anchor8)

<a id="anchor1"></a>
## <a href="#anchor1">Webオートメーションとは?</a>
直訳すると「Webの自動化」、つまりブラウザ操作をプログラムに肩代わりさせるという手法です。
従来の手法としてよく知られているのは、サーバーから取得したHTMLをパースし、セレクタで要素を取得し、次のリクエストを構成してサーバーへGET/POSTするといったものです。
Webオートメーションの技術を用いると、仮想ブラウザ上においてHTMLのみならず、JavaScript/CSS/セッション情報などを全て含めてレンダリングすることで、「あたかもページを見ているように」要素を取得/操作したり、フォーム上からリクエストを送信することができます。これにより直感的な操作の自動化が可能になります。
多くのツールが、任意のタイミングで仮想ブラウザ画面のスナップショットを保存することができるのも特徴となっています。

<a id="anchor2"></a>
## <a href="#anchor2">Webオートメーションを可能にするプロダクト</a>

<a id="anchor3"></a>
### <a href="#anchor3">PhantomJS</a>
[PhantomJS](http://phantomjs.org/)は、ヘッドレスWebKitをJavaScriptから操作するためのAPIを提供します。
node.jsにおいてメジャーなテクノロジーとなっており、テストツールとしての機能を提供するCasperJSやSpookyJSと組み合わせてWeb UIテストの自動化、スクレイピングなどに利用されています。

<a id="anchor4"></a>
### <a href="#anchor4">Selenium</a>
[Selenium](http://docs.seleniumhq.org/)は、Java言語におけるWeb UIテストツールとして発展してきました。FireFoxやChromeのバイナリ、あるいはPhantomJSが提供するヘッドレスWebKitにアクセスしてDOMを構築、検証します。
スクレイピングツールとしても十分な機能を有しますが、テストツールとして発展してきた経緯があるため、単純にスクレイピングに使用したい時には持て余してしまう印象があります。

<a id="anchor5"></a>
### <a href="#anchor1">Ui4j</a>
[Ui4j](https://github.com/ui4j/ui4j)は、Javaで実装された軽量なWebオートメーションツールです。
ブラウザとしてはJavaFX/OpenJFXに含まれるWebKitを使用しています。
他にバイナリ等の用意が必要ないことや、ポートを使用しないという点からスクレイピングツールとして手軽に使用することができます。

<a id="anchor6"></a>
## <a href="#anchor6">Ui4jの使用例</a>
JavaにおけるWebオートメーションツールの使用例として、Ui4jを使ったサンプルを記載します。
Mavenを使用している場合、dependencyにcom.ui4j.ui4j-all および org.jfxtras.openjfx-monocle を追加します。

```xml:pom.xml
<dependency>
    <groupId>com.ui4j</groupId>
    <artifactId>ui4j-all</artifactId>
    <version>2.1.0</version>
</dependency>
<dependency>
    <groupId>org.jfxtras</groupId>
    <artifactId>openjfx-monocle</artifactId>
    <version>1.8.0_20</version>
</dependency>
```


以下に、ニコニコ生放送サイトにおいて番組を検索するサンプルを示します。


```java:App.java
import com.ui4j.api.browser.*;
import com.ui4j.api.dom.*;
import com.ui4j.api.event.DocumentLoadEvent;
import java.io.*;
import java.util.Optional;

public class App {

    public static void main(String[] args) throws InterruptedException, FileNotFoundException {
        // ヘッドレス設定をtrue (ブラウザをバックグラウンドで実行し、ポップアップさせない)
        System.setProperty("ui4j.headless", "true");
        PageConfiguration config = new PageConfiguration();
        config.setUserAgent("Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:43.0) Gecko/20100101 Firefox/43.0");
        Page page = BrowserFactory.getWebKit().navigate("http://live.nicovideo.jp/", config);
        page.show(true); // スナップショットを取得するのに必要
        page.addDocumentListener((DocumentLoadEvent dle) -> {
            try {
                // スナップショットをローカルへ書き出し
                page.captureScreen(new FileOutputStream(new File("ss" + System.currentTimeMillis() + ".png")));
            } catch (FileNotFoundException ex) {
            }
        });
        Document doc = page.getDocument();
        // 検索フォームに値 "声優"をセット
        doc.query("#header_search_form_input").get().setValue("声優");
        // 検索フォームのボタンをクリック
        doc.query("#header_search_form button[type=submit]").get().click();
        // タブ「放送予定」をクリック
        doc.query("#select_mode_area #mode_reserved_div").get().click();
        Thread.sleep(500);
        // チェックボックス「公式」をクリック
        doc.query("#provider_official_button").get().click();
        Thread.sleep(500);
        // チェックボックス「チャンネル」をクリック
        doc.query("#provider_channel_button").get().click();
        Thread.sleep(500);
        // ページャーの「次へ」をクリック
        Optional<Element> query = doc.query(".pager a.next");
        // 「次へ」の要素がある間、続ける
        while(query.isPresent()){
            query.get().click();
            query = doc.query(".pager a.next");
        }
    }
}
```

各操作ポイントで必要になる要素名は、一般的なWebブラウザに備わっている要素検証ツールで調べながら記述しています。
Webオートメーションツールの多くには、実行中にスナップショットを保存する機能があるため、実際のページの遷移と見比べながら、直感的にコードを記述していくことが可能です。
同様のコードによって、各種Webサービスにログインを行うこともきわめて容易に実現できます。

<a id="anchor7"></a>
## <a href="#anchor7">Webオートメーションツールを使うメリット</a>

JsoupなどのHTMLパーサーを使ってスクレイピングを行う場合、

- 検索/ログインフォームにおいて、どういったパラメータで情報がサーバーとやりとりされているのか
- リクエストに対してどのような形式で応答が返ってきて、それがどうDOMに反映されるのか
- セッション情報がどう管理されているのか

など、自動化を実現する上で理解が必要な項目が多岐に渡ります。
もちろんJsoupでも時間をかければ実現可能なのですが、調査を進める中で壁にぶち当たってしまうことも少なくありません。
仮想のブラウザ上でDOMを構築し操作するWebオートメーションの技術は、Webスクレイピングをもっとカジュアルなものにしてくれます。
必要になるのはjQueryライクなセレクタの知識くらいで、すでにJsoupなどのjQueryライクなHTMLパーサーを使っている人ならば、これらのWebオートメーションツールをすぐに使うことができると思います。

<a id="anchor8"></a>
## <a href="#anchor1">Webオートメーションツールを使う際の注意点</a>

仮想ブラウザ上でDOMを構築し操作するWebオートメーションツールは、HTMLに記載のあるJavaScriptなどを全て読み込んだ上でレンダリングを行うため、特にページ読み込み時のイニシャルコストが高くつきます。
10ページ、100ページと続けて読み込ませると、応答が致命的に遅くなってしまいます。
動的でないページの取得には、従来のHTMLパーサーを使うなどして、上手く棲み分けを行うと良いと思います。
（検索はUi4jから行い、個別ページのURLが得られたらJsoupでパースするなど）