私はFlutterを使って趣味でアプリを作っています。その中でログイン操作が必要なサイトからスクレイピングをする必要がありました。
そこで、今回行ったスクレイピングの方法について書きます。
概要
FlutterでWebViewを使ったスクレイピングをしました。
WebViewとは
WebViewはスマートフォンのアプリ上で開けるブラウザのようなものです。
アプリでボタンを押し、Webページが開いてきたら大体それがWebViewです。
基本的にバグ報告フォームのようにアプリの機能の一部に埋め込まれることが多いですが、たまにアプリ全体がWebViewでできており、中身が全てWebViewのブラウザ上で動く、なんてアプリもあります。
なぜWebViewを使ったのか?
FLutterにはuniversal_htmlという便利なプラグインがあります。
別にブラウザなんか使わなくても簡単にhtmlファイルを取得できます。ですので、わざわざ描画の処理までするWebViewを使う必要は基本的にはないです。え...
ではなぜ、今回スクレイピングにWebViewを使ったのか???
今回スクレイピングでデータを取ってきたいページはログインが必要なサイトでした。
universal_htmlでもIDとパスワードを入力して、ログインボタンを押す、という操作は可能なので簡単なサイトなら普通にログインしてhtmlファイルを取得できます。
しかし、今回のサイトはなんかSSOとか複雑な処理をしているらしく(複数のhttpリクエストが必要らしい)
universal_htmlだと、ログイン後のhtmlファイルが返ってきませんでした。
ログインの際の細かい仕様が分かればuniversal_htmlでいけるかもしれない、、、けどかなり複雑そうなので諦めました。
しかし当然ブラウザを経由してアクセスすればログインできます。
そこで仕方なくWebViewを使ったということです。
今回は以下の流れでスクレイピングを行うWidgetを作成しました。
- ログインページに飛ぶ
- サイトに自動でログインする
- 目的のページに飛ぶ
- htmlファイルを取得する
自動ログインをする
スクレイピングをする前にサイトにログインする必要があります。
WebViewではこちらが入力したJavaScriptを実行することができます。
以下のコードでログインページでログイン操作を行います。
WebView(
initialUrl: 'https://temple/login',
// jsを有効化
javascriptMode: JavascriptMode.unrestricted,
// controllerを登録
onWebViewCreated: _controller.complete,
// ページの読み込み開始
onPageStarted: (String url) {},
// ページ読み込み終了
onPageFinished: (String url) async {
final controller = await _controller.future;
//ログインページ表示時
if(url == 'https://temple/login') {
//IDを入力
await controller.runJavascript('document.getElementsByName("USER")[0].value="' + userID + '";');
//パスワードを入力
await controller.runJavascript('document.getElementsByName("PASSWORD")[0].value="' + passWord + '";');
//ログインボタンをクリック
await controller.runJavascript('document.getElementById("Login").click();');
}
}
),
WebViewで一番初めに表示するURLをログインページに設定しました。
ログインページが読み込まれるとonPageFinishedが走り、
3つのrunJavaScriptが実行されます。
最初の2つはuserID、PASSWORDの要素にこちらが用意したID,PWを入力します。
最後にボタンの要素を取得し、クリック操作を行います。
これでブラウザ上でログインが完了します。
htmlファイルを取得する
ログイン後のトップページから目的のページに飛び、htmlファイルを取得します。
WebView(
initialUrl: 'https://temple/login',
// jsを有効化
javascriptMode: JavascriptMode.unrestricted,
// controllerを登録
onWebViewCreated: _controller.complete,
// ページの読み込み開始
onPageStarted: (String url) {},
// ページ読み込み終了
onPageFinished: (String url) async {
// ページタイトル取得
final controller = await _controller.future;
//ログインページ表示時
if(url == 'https://temple/login') {
//IDを入力
await controller.runJavascript('document.getElementsByName("USER")[0].value="' + userID + '";');
//パスワードを入力
await controller.runJavascript('document.getElementsByName("PASSWORD")[0].value="' + passWord + '";');
//ログインボタンをクリック
await controller.runJavascript('document.getElementById("Login").click();');
}
//サインイン成功時
if(url == 'https://temple/home') {
//データページをロード
await controller.loadUrl('https://temple/data');
}
//データページ表示時
if(url == 'https://temple/data'){
//データを取得
final data = await controller.runJavascriptReturningResult("window.document.querySelector('.data').innerHTML;");
}
}
),
ログインページでサインインが成功したら、データページのURLがロードします。
データページが読み込まれると
runJavascriptReturingResult()で返り値を取得します。
今回はdataクラス内のhtmlを返り値として取得しています。
これでログインが必要なサイトからスクレイピングをすることができました。
ブラウザを使うため、スクレイピングとしては少し非効率的な方法です。
しかし複雑なサイトでも確実にスクレイピングすることができます。