こんにちわ、みけです。
昨日(2013/08/19)に開催されたJJUGのLT大会でLTをしてきました。
LTの内容ですが、
ネタLTとしては大成功(?)でしたが、
まあJavaのLTとしては疑問符が残るものを発表しました。
とりあえず、JavaFXの部分だけ抽出して書きたいと思います。
要望・仕様
ウケ狙いをしたLTの「冒険者の広場』のページについて、次のような要望・仕様になっています。
- 定期的にWebの情報を取得したい
- Web APIが公開されていない
- リンク先のURLがJavascriptでのクリックにバインドされた関数に記載されている
スクレイピングツールの候補
Javaで利用できるスクレイピングツールおよびブラウザーは次のものが主に挙げられます
- NekoHTML/Jsoup
- HtmlUnit
- Selenium
- JavaFXのWebEngine
NekoHtml
Java1.3以降から使えるHTMLパーサーです。
古くてかつ最近は更新が遅いようです。
最新のHTMLをパースするのには向いていません。
パースするだけなのでJavascriptの実行はできません。
したがって、採用しませんでした。
Jsoup
HTML5のタグにも対応したHTMLパーサーです。
要素の取得などは非常に便利なのですが、
これもHTMLをパースするだけでJavascriptの実行はできません。
したがって、採用しませんでした。
HtmlUnit
NekoHtmlをHTMLパーサーとして利用したヘッドレスブラウザーです。
Javascriptの実行にRhinoを使っています。
僕自信の目的にはこれで十分な機能だったのですが、
残念ながら『冒険者の広場』を表示しようとしたら、
Javascriptエラーが発生したので、
採用しませんでした。
Selenium
言わずと知れたFireFoxの自動実行ツールです。
ブラウザの操作を覚えさせることも可能なため、
比較的スクリプトを作るのが楽です。
しかし、個人的にはブラウザーがポコポコ立ち上がるのが
好きでないので採用しませんでした。
JavaFXのWebEngine
レンダリングエンジンにWebKitを採用したブラウザーです。
Javascriptの実行にRhinoが使われています。
JavaFXのアプリケーション側からJavascriptの実行も指示することができます。
好みであれば画面を立ち上げることなしに実行できます。
特に文句をつけるところもないので採用しました。
JavaFXをヘッドレスブラウザーとして使う時のやり方
Application
を継承したクラス
JavaFXのアプリケーションですので、Application
クラスを継承したクラスを作成します。
1 2 3 4 5 6 7 8 9 |
|
ここでのポイントは2つです。
start
メソッドの中でWebEngine
のインスタンスを生成すること- 特に画面を表示する必要がないので、
stage.show()
を実行しないこと
JavaFXの起動と終了
Application.launch(Class<? extends Application>)
を実行すればいいのですが、
これを実行するとJavaFXアプリケーションを実行している間は
何もできなくなるため、別のスレッドで実行する必要があります。
1 2 3 4 5 6 7 8 9 10 |
|
JavaFXの終了にはPlatform.exit()
を実行します。
これによりJavaアプリケーション自体が起動したままになるのを防ぐことができます。
Javascriptの実行
WebEngine
のexecutScript(String)
を実行します。
1 2 3 4 5 6 7 8 9 |
|
WebEngine
の操作はJavaFXのApplication Threadから実行しなければなりません。
そのために、Platform.runLater(Runnable)
を介して実行する必要があります。
引数はRunnable
のために、戻り値を利用することができません。
そのために、BlockingQueue<T>
を使っています。
WebEngine#executeScript(String)
の戻り値をここではObject
として利用していますが、
戻り値は実際には次のようにマッピングされます。
Javascriptでの型 | Javaでの型 |
---|---|
boolean | Boolean |
number(整数値) | Integer |
number(少数値) | Double |
string | String |
object | netscape.javascript.JSObject |
function | netscape.javascript.JSObject |
null | null |
Javascriptでの配列はobject
ですので、JSObjectにマッピングされます。
JSObject
のあ使い方ですが、getMember(String)
とgetSlot(int)
を用いて値にアクセスします。
getMemberメソッド
{hoge : "hoge", foo : 10}
というオブジェクトの場合、
次のようにgetMember(String)
メソッドを使用して値を取り出します。
1 2 |
|
getSlotメソッド
[1,2,3,4,5]
という配列の場合、
次のようにgetSlot(int)
メソッドを用いて値を取り出します。
1 2 3 |
|
WebEngineの同期
先ほど書いたように、WebEngineへの操作は別スレッドで実行することになりますが、
さらにJavaFX ApplicationスレッドとWebEngineは異なるスレッドで実行されています。
したがって、WebEngineの動作に合わせてアプリケーションの実行をしたいシーンが発生すると思います。
その場合、getLoadWorker
メソッドでWorker
を取得して、getState
にて
WebEngineの状態を確認する必要があります。
1 2 3 4 5 6 7 8 9 10 |
|
この結果がWorker.State.RUNNING
の間は、
Javascriptの実行などをしているため、
戻り値を利用するなどのアプリケーション操作はできません。
したがって、Thread.sleep(long)
等で停止しておくとよいでしょう。
1 2 3 4 5 6 7 |
|
おわり
以上が昨日全くもって話しえなかったことです。
スレッド周りが非常に面倒な感じがしますが、
慣れればあとはJavascriptを好きに実行できるので、
WebアプリでのJavascriptのテストなどで活用できたりします。