17
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PhantomJS で Content-Disposition: attachment なレスポンスを取得する方法

Posted at

はじめに

とあるサイトに定期的にログインして利用履歴のCSVを取ってくるというプログラムを AppleScript + Safariで書いていたがいちいち Safari が起動してめんどくさいので、PhantomJS で書きなおすことにした。

さっそく PhantomJS の最新版(1.9.2) をインストールして、スクリプトを書いた。コードは下記のとおりである(簡易化してある)。

download_csv.coffee
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
/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
/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を指定する。なお、レスポンスを文字列として直接得る方法はないようだ。

download_csv.coffee
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 にアップロードしてある。

17
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?