AWS Lambda上でPhantomJSを使う

[PhantomJS Lambda Template](https://github.com/justengland/phantom-lambda-template)の開発者が [phantomjs-lambda-pack](https://github.com/justengland/phantomjs-lambda-pack)  を新しくリリースしていたので、これを使ってLambda上でPhantomJSを動作させてみます。

### 環境構築と初期設定

```
$ mkdir lambda-phantomjs && cd  lambda-phantomjs
$ npm init
$ npm install phantomjs-lambda-pack --save
```

index.jsはREADME.md のusageに書かれている内容でまずは試します。

```
% cat index.js 
'use strict';
const phantomjsLambdaPack = require('phantomjs-lambda-pack');
const exec = phantomjsLambdaPack.exec;

exports.handler = (event, context, callback) => {
    exec('-v', (error, stdout, stderr) => {
        if (error) {
            console.error(`exec error: ${error}`);
            return;
        }

        console.log(`phantom version: ${stdout}`);
        console.log(`Should have no error: ${stderr}`);

        callback(error, 'fin!!');
    });
};
```

#### Lambda Functionの設定と実行

Function Packageをアップロードしたら以下のように設定します。
（使用するスクリプトは、Code entry typeで、『Upload a .ZIP file』を選択してアップロードすることができるサイズ(10MB以下）になっているかと思います。）

|設定項目  |設定値  |
|---|---|
| Runtime | Node.js 4.3 |
| Handler | lambda-phantomjs/index.handler |
| Memory(MB) | 1024 |
| Timeout | 5 min |

初回実行時は 1min だと Timeout エラーが発生したので、5min としています。
実行結果を見ると2分ぐらいを見ておけば大丈夫かもしれません。
（2回目以降の実行時は 4000 ms 前後ぐらいで安定します。）

##### 実行結果

Log output を確認すると以下のように実行されたことがわかります。

```
2016-11-24T10:02:46.845Z	f39f30db-b22c-11e6-94f0-653737dc93be	exec phantom:  -v
2016-11-24T10:02:47.105Z	f39f30db-b22c-11e6-94f0-653737dc93be	phantom version: 2.1.1

2016-11-24T10:02:47.105Z	f39f30db-b22c-11e6-94f0-653737dc93be	Should have no error: 
END RequestId: f39f30db-b22c-11e6-94f0-653737dc93be
REPORT RequestId: f39f30db-b22c-11e6-94f0-653737dc93be	Duration: 75160.69 ms	Billed Duration: 75200 ms 	Memory Size: 1024 MB	Max Memory Used: 401 MB	PhantomJS
```

phantomjs-lambda-pack を使うと PhantomJS の 2.1.1 が使われていることがわかります。
内部では [phantomjs-prebuilt](https://www.npmjs.com/package/phantomjs-prebuilt) が使われているので、phantomjs-prebuilt が対応しているバージョンの PhantomJS が使われることになります。

### 実際にPhantomJSを使ってScraping

GoogleにアクセスしてPageのTitleを取得、および表示したページのスクリーンショットをS3にアップロードしてみます。
ディレクトリ構造は下記の通りです。
index.js と同じディレクトリ階層に phantomjs-script.js というスクリプトを作成しています。

```
.
├── index.js
├── node_modules
│   ├── aws-sdk
│   └── phantomjs-lambda-pack
│
├── package.json
└── phantomjs-script.js
```

事前に aws-sdk のインストールおよび、Lambdaの実行ロールにS3 への書き込み権限を付与しておきます。
handler の中で phantomjs-script.js を呼び出しています。

```index.js
'use strict';
const phantomjsLambdaPack = require('phantomjs-lambda-pack');
const exec = phantomjsLambdaPack.exec;
const path = require('path');
const fs = require('fs');
const AWS = require('aws-sdk');
AWS.config.region = 'ap-northeast-1';

exports.handler = (event, context, callback) => {
    const scriptPath = path.join(__dirname, 'phantomjs-script.js');
    exec(scriptPath, (error, stdout, stderr) => {
        if (error) {
            console.error(`exec error: ${error}`);
            return;
        }

        console.log(`Result: ${stdout}`);
        console.log(`Should have no error: ${stderr}`);

       // スクリーンショットをS3にアップロードする
       const tmp_file_path = path.join(__dirname, 'google.png');
       const params = {Bucket: 'bucketName', Key: 'screen/google.png', Body: fs.createReadStream(tmp_file_path),};
       new AWS.S3().upload(params, (error, data) => {
           callback(error, 'fin!!');
       });
    })
}
```

```phantomjs-script.js
// headlessブラウザを作成
var page = require('webpage').create();
page.settings.userAgent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36';
page.settings.javascriptEnabled = true;
page.settings.loadImages = false;//Script is much faster with this field set to false
phantom.cookiesEnabled = true;
phantom.javascriptEnabled = true;

//URLを開く
page.open('http://www.google.com', function(status) {
  //ブラウザ内でJSを介してデータを取得  
  var title = page.evaluate(function() {
    return document.title;
  });
  page.render('google.png');
  console.log('Page title is ' + title);
  phantom.exit();
});


```

実行結果として Log output に Result: Page title is Google が出力され、以下のようなスクリーンショットが確認できれば、問題なく実行されたことになります。

![google.png](https://qiita-image-store.s3.amazonaws.com/0/17814/e673fccc-f6c7-00c6-d750-b2891440a6e1.png)

### Tipsとか

#### jQueryを使いたい

jQueryを使ってDOMをパースしたいときは以下のように includeJs を使って書けます。

```phantomjs-script.js
//省略

page.open('http://www.google.com', function(status) {
    page.includeJs('https://code.jquery.com/jquery-3.1.1.slim.min.js', function() {
        //ブラウザ内でJSを介してデータを取得  
        var title = page.evaluate(function() {
            return $('title').text();
        });
        console.log('Page title is ' + title);
        phantom.exit();
    });
});
```

#### PhantomJSのスクリプトにパラメータを渡したい
execの引数として配列を渡し、PhantomJSのスクリプトで system.args を利用できるようにしておくことでパラメータが渡せます。


```index.js
// 省略

exports.handler = (event, context, callback) => {
    const scriptPath = path.join(__dirname, 'phantomjs-script.js');
    const childArgs = [
        scriptPath,
        'args1',
    ];
    exec(childArgs, (error, stdout, stderr) => {
        if (error) {
            console.error(`exec error: ${error}`);
            return;
        }

        console.log(`Result: ${stdout}`);
        console.log(`Should have no error: ${stderr}`);
    });
```

```phantomjs-script.js
var system = require('system');
var args = system.args;

console.log(args.length);
console.log(args[1]);

// 省略
```

#### evaluateに外部で定義した変数を使用する

外部で定義した変数を使う場合は以下のようにします。

```phantomjs-script.js
  var hoge = 'fuga';
  var title = page.evaluate(function(s) {
    console.log(s);
    return document.title;
  }, hoge);
```