はじめに
とあるサイトに定期的にログインして利用履歴のCSVを取ってくるというプログラムを AppleScript + Safariで書いていたがいちいち Safari が起動してめんどくさいので、PhantomJS で書きなおすことにした。
さっそく PhantomJS の最新版(1.9.2) をインストールして、スクリプトを書いた。コードは下記のとおりである(簡易化してある)。
page = (require 'webpage').create()
callbacks = [
-> # ログイン画面の処理
page.evaluate ->
document.getElementById('user').value = 'user'
document.getElementById('pass').value = 'pass'
e = document.createEvent('MouseEvents')
e.initEvent('click',false,true)
document.getElementById('submit').dispatchEvent(e)
-> # ホーム画面の処理
page.evaluate ->
e = document.createEvent('MouseEvents')
e.initEvent('click',false,true)
document.getElementById('to_history').dispatchEvent(e)
-> # 履歴画面の処理 -> CSVをダウンロードする!
page.evaluate ->
e = document.createEvent('MouseEvents')
e.initEvent('click',false,true)
document.getElementById('download').dispatchEvent(e)
]
page.onLoadFinished = ->
callback = callbacks.shift()
phantom.exit() unless callback
callback()
page.open "http://localhost:9292"
しかし、肝心のCSVの取得がうまくいかない。というよりも、どうやってCSVのレスポンスを得るのかがわからない。ファイルとしてどこかに保存されているわけでもないようだ。
いろいろ調べた結果、公式に#10052: File downloadという issue があり、そのスレッドによると、 Content-Disposition: attachement なレスポンスは取得できない ようだ。
しかし、同スレッドで公式から fork された add_download_capabilities というバージョンなら対応してるらしいということが分かった。なお、ベースとなった PhantomJS のバージョンは 1.6 である。
インストール
さっそく git clone してビルドしてみる(ビルド方法については公式を参照) 。環境は、CentOS 6.3 x86_64。ビルドには2時間くらいかかった。
$ sudo yum install gcc gcc-c++ make git openssl-devel freetype-devel fontconfig-devel
$ git clone https://github.com/woodwardjd/phantomjs.git
$ cd phantomjs
$ git checkout add_download_capabilities
$ ./build.sh
ビルドが終了した後、所定のディレクトリに移動し PATH を通す。今回は、/usr/local/phantomjs/add_download_capabilities
とした。
$ mv phantomjs add_download_capabilities
$ sudo mkdir -p /usr/local/phantomjs/
$ sudo mv add_download_capabilities /usr/local/phantomjs/
$ sudo vi /etc/profile.d/phantomjs.sh
export PATH=/usr/local/phantomjs/add_download_capabilities/bin:${PATH}
これでインストールは完了のはずだが、実行すると下記のエラーがでる。
$ phantomjs --version
phantomjs: error while loading shared libraries: libQtWebKit.so.4: cannot open shared object file: No such file or directory
{PhantomJSディレクトリ}/src/qt/lib
に libQtWebKit.so.4 があったので、/etc/ld.so.conf.d/phantomjs.conf
にパスを追加した後 ldconfig する。
$ sudo vi /etc/ld.so.conf.d/phantomjs.conf
/usr/local/phantomjs/add_download_capabilities/src/qt/lib
$ sudo ldconfig
なんとか PhantomJS のインストールができた。
$ phantomjs --version
1.6.1
Content-Disposition: attachement なレスポンスの取得
この fork されたバージョンでは、WebPage
オブジェクトに新たに、 WebPage#onUnsupportedContentReceived()
コールバックが追加されており、これで Content-Disposition: attachment なレスポンスをフックする。
レスポンスの保存には、WebPage#saveUnsupportedContent()
メソッドを用いる。第一引数にファイル名、第二引数にレスポンスIDを指定する。なお、レスポンスを文字列として直接得る方法はないようだ。
page = (require 'webpage').create()
callbacks = [
-> # ログイン画面の処理
page.evaluate ->
document.getElementById('user').value = 'user'
document.getElementById('pass').value = 'pass'
e = document.createEvent('MouseEvents')
e.initEvent('click',false,true)
document.getElementById('submit').dispatchEvent(e)
-> # ホーム画面の処理
page.evaluate ->
e = document.createEvent('MouseEvents')
e.initEvent('click',false,true)
document.getElementById('to_history').dispatchEvent(e)
-> # 履歴画面の処理 -> CSVをダウンロードする!
page.evaluate ->
e = document.createEvent('MouseEvents')
e.initEvent('click',false,true)
document.getElementById('download').dispatchEvent(e)
]
page.onLoadFinished = ->
callback = callbacks.shift()
phantom.exit() unless callback
callback()
# Content-Disposition: attachment なレスポンスが返された時に呼び出されるコールバック
page.onUnsupportedContentReceived = (response) ->
page.saveUnsupportedContent('tmp/history.csv', response.id)
page.open "http://localhost:9292"
また、onUnsupportedContentReceived()
の引数、response
の内容は下記の通りである。
{
"contentType": "text/csv", // サーバーから返されたContent-Type
"headers": [ // サーバーから返されたHTTP-Header
{
"name": "Content-Type",
"value": "text/csv"
},
{
"name": "Content-Disposition",
"value": "attachment; filename=\"history.csv\""
},
{
"name": "Content-Length",
"value": "94"
},
{
"name": "Server",
"value": "WEBrick/1.3.1 (Ruby/1.9.3/2012-12-25)"
},
{
"name": "Date",
"value": "Fri, 22 Nov 2013 12:28:47 GMT"
},
{
"name": "Connection",
"value": "Keep-Alive"
}
],
"id": 1, // レスポンスID。saveUnsupportedContent()メソッドで必要となる。
"redirectURL": null,
"status": 200, // サーバーが返したHTTPステータスコード
"statusText": "OK ", // サーバーが返したHTTPステータス説明句
"time": "2013-11-22T12:28:47.221Z",
"url": "http://localhost:9292/download_csv" // リクエストURL
}
おわりに
この問題、結構基本的なことだと思うが、何故かほとんど情報がなく今回の記事を書くこととなった。
公式もはやくこの問題を解決してほしいものである。
なお、この記事で示したコードは GitHub にアップロードしてある。