ブラウザの自動操作で手軽さを追求したらAWS LambdaとPhantomJSの組み合わせにたどり着いた

#はじめに

---
:warning: **2018/07/31追記 PhantomJSについて**
こちらで紹介したPhantomJSは現在、リポジトリがアーカイブされて利用は**非推奨**です。

手軽に実装すらなら現在は以下とpuppeteerの組み合わせがオススメです。
https://www.npmjs.com/package/@serverless-chrome/lambda

こちらの組み合わせの記事ついては別途紹介記事を書きます。

---


DMM.com #1 Advent Calendar 2017 の14日目を担当します、 @mesh1neko  です。

普段はAWSやNode.jsをメインに扱っています :muscle:
今日はブラウザ操作を極力お手軽にやる組み合わせについて書きます。

▼このAdvent Calendar について
前日の記事は、 @canacel さんの [ReduxにImmutable.JSを適用してみた](https://qiita.com/canacel/items/ae6285bc12f4a2d30128) 
でした。
これは DMM.com #1 Advent Calendar 2017 - Qiita の14日目の記事です。

qiita.com
カレンダーのURLはこちら
[DMM.com #2 Advent Calendar 2017 - Qiita](https://qiita.com/advent-calendar/2017/dmm2)
[DMM.com #1 Advent Calendar 2017 - Qiita](https://qiita.com/advent-calendar/2017/dmm)

#tl;dr
お手軽さを追求するとlambdaとPhatomJSの組み合わせが楽

* Lambdaの制限を頑張らなくても回避できる

#対象読者
とにかくお手軽にブラウザ操作を自動化したい方向けです。

ブラウザの自動操作というとE2Eテストのようですが、
本記事は、いかに楽しつつ、ルーチンワークで楽するかを考えてる方向きとなります。
（きっとE2Eについては 16日目の @mt0m さんが書いてくれるはず...！）


#モチベーション
**ルーチンでやっている事務作業をできるだけ楽に解決したい**

弊社では、MTG予定とMTG施設が社内システムで統合的に管理されています。
そのため、自分が設定したMTGはさておき、
MTGに召集された場合、社内システムをわざわざ開いて確認する必要がありました。

もちろん、当該社内システムにも通知機能はありますが、システムを開かないとその通知を確認できず
結局1日1回、google calendarにMTG予定を転記するという
マンパワー全開の方法にて参加忘れ防止を図っていました... :cry: 

**なんとか、この毎日の作業をなくして気持ちよく仕事を始めたい...！**

#お手軽な組み合わせ検討
さて、お手軽に毎日スケジュール実行させたいとなると
サーバもいらない・ログも残る...ということでAWS Lambdaとの組み合わせを考えていきたいと思います。

## 候補

まずは候補として以下を選んでみました。
選定理由に厳密な理由はなく、自分が把握している範囲で思いついたものを選んでいます。

|候補|理由|
|:-- |:-- |
|[GoogleChrome/puppeteer](https://github.com/GoogleChrome/puppeteer)|最近のE2Eといえばpuppeteerだと話題があったので|
|[grahcool/chromeless](https://github.com/graphcool/chromeless)|少し前にgithubでトレンド入りしていた|
|[segmentio/nightmare](https://github.com/segmentio/nightmare)|去年くらいまではNightmareというイメージがあった|
|[ariya/phantomjs](https://github.com/ariya/phantomjs)|ブラウザ操作の老舗的存在のイメージがあった|

## 調査・比較
Lambdaとの組み合わせで、いかに手軽に実現できるかどうかを念頭におきつつ調査・比較してみました。

## 信頼性
単純にgithubのstar数で比較しました。

||信頼性|
|:-- |:--:|
|GoogleChrome/puppeteer|◎|
|grahcool/chromeless|○|
|segmentio/nightmare|○|
|ariya/phantomjs|◎|

star数で見ると調べた時点でpuppeteerが20,251、phantomjsが23,957。
puppeteerの勢いのすごさを実感しますね :innocent:

## 継続性
こちらは最近のcommit状況や開発元などを参考にしています。

||継続性|
|:-- |:--:|
|GoogleChrome/puppeteer|◎|
|grahcool/chromeless|○|
|segmentio/nightmare|○|
|ariya/phantomjs|△|

puppeteerはChromeのDevToolの開発チームということで、日々の開発も活発なようです。
chromelessやnightmareはpuppeteerほどではありませんが、そこそこ開発が進んでいるようです。
一方でphantomjsは唯一のメンテナさんが対応stop宣言をされたことで、先は見えない状況です。
cf. https://groups.google.com/forum/m/#!topic/phantomjs/9aI5d-LDuNE

## 日本語記事
これはqiitaの投稿数でみています。

||日本語記事|
|:-- |:--:|
|GoogleChrome/puppeteer|○|
|grahcool/chromeless|△|
|segmentio/nightmare|○|
|ariya/phantomjs|◎|

登場が早い分phantomjsの記事がやはり多かったです。
githubで話題になったわりに、chromelessの記事は少なかったのが意外でした。

## セットアップ難易度
lambda上で実現する場合の難易度を比較しています。

||セットアップ難易度|
|:-- |:--:|
|GoogleChrome/puppeteer|×|
|grahcool/chromeless|×|
|segmentio/nightmare|×|
|ariya/phantomjs|◎|

lambdaには容量の制限と、言語のバージョン縛りがある点で差がでました。
puppeteerは最低でも6.4以上が必要となり、Lambda上で動かすにはトランスパイルが必須となります。
また、phantomjs以外のヘッドレスブラウザをlambda上で動かすためには細工も必要です。

具体的にいうと、puppeteerとchromelessが使うheadless chromeやNightmareが使うElectronといった
ヘッドレスブラウザをAmazon Linux環境で動かすためには専用にビルドしてlambdaパッケージに含める必要があります。
しかし、このパッケージに含める際にも50Mの容量上限が問題となります...。

なんとか50M以内に抑えるか、実行時にS3から引っ張ってくるといった事例は既に事例があります。
cf. [Lambda上でPuppeteer/Headless Chromeを動かすStarter Kitを作った - sambaiz-net](https://www.sambaiz.net/article/132/)
cf. [dimkir/nightmare-lambda-tutorial](https://github.com/dimkir/nightmare-lambda-tutorial)

この点、PhantomJSは環境変数の指定だけで問題なくデプロイパッケージに含める（33Mほど）ことができました。

```javascript
PHANTOMJS_PLATFORM="linux"
PHANTOMJS_ARCH="x64"
```

## 実行時間
lambdaで動かした場合の実行時間です。

||実行時間|
|:-- |:--:|
|GoogleChrome/puppeteer|△|
|grahcool/chromeless|△|
|segmentio/nightmare|△|
|ariya/phantomjs|○|

単純にコードの時間だけでみるとpuppeteerが一番だと思いますが、
さきに述べたヘッドレスブラウザをどこから調達するかで初回のオーバヘッドがかなり変わってきます。
S3から取得するパターンの場合、継続して実行したら早いけど、毎回最初の1回目が遅くなってしまいます。

コードの実行自体に時間はかかりますが、ヘッドレスブラウザ自体をパッケージに含められ
初回においても実行時間が安定しています。
今回の手軽さという用途に、より適しているとしてPhantomJSが他の候補より一歩リードしました。

## 書きやすさ
コード自体の書きやすさです。

||書きやすさ|
|:-- |:--:|
|GoogleChrome/puppeteer|◎|
|grahcool/chromeless|○|
|segmentio/nightmare|○|
|ariya/phantomjs|△|

PhantomJS以外はどちらかというと、やりたいことを素直に書けます

```javascript:pupper/chromeless/nightmare
    .goto('https://httpbin.org/forms/post')
    .type('input', 'body > form > p:nth-child(1) > label > input');
```

　一方でPhantomJSではそういったメソッドはないため、
evaluateで擬似的に値をセットするという、まわりくどい感じとなります。

```javascript:phantomjs
yield page.open('https://httpbin.org/forms/post');
yield page.evaluate(function() {
    document.forms[0].custname.value = 'input';
});
```

evaluateのスコープ内に変数を持っていく場合も注意が必要です。

```javascript
const name = 'hoge';
yield page.evaluate(function(arg1, arg2) {
    // この場合'hoge'は入らない
    //document.forms[0].custname.value = name;
    document.forms[0].custname.value = arg2;
}, name, 'hyooo');
```

# 比較まとめ

||信頼性|継続性|日本語記事|セットアップ難易度|実行時間|書きやすさ|
|:-- |:--:|:--:|:--:|:--:|:--:|:--:|
|GoogleChrome/puppeteer|◎|◎|○|×|△|◎|
|grahcool/chromeless|○|○|△|×|△|○|
|segmentio/nightmare|○|○|○|×|△|○|
|ariya/phantomjs|◎|△|◎|◎|○|△|


PhantomJSは今後のメンテナンスが状況がわからないため、この先ずっと使える保証はないですが、
当面は、お手軽さを追求するとlambdaとPhatomJSの組み合わせが楽そうだな、という結果になりました。
Lambdaの制限が緩和されるともっと楽にかける日がくるかもしれません :innocent: 

# Lambda上でのPhantomJSの使い心地
さて、冒頭で触れたGoogleカレンダーにMTG予定を転記する作業を自動化が今回の目的でした。

PhatomJS冗長な記述で実は大変なのでは...と思いきや

**あっさりできました:innocent:**

evaluateで大抵の操作をすることになるので記述がどうしても冗長にはなりますが、
ブラウザの開発コンソールで試したことを、そのままコードに書いて動かせる点は
いちいちAPIを調べなくてもよいので、その点でも楽さを感じました :smile: 

なお、今回はgoogle-calendarへの情報登録にあたり
以下のライブラリを使わせていただきました。
こちらの解説は本筋がずれるので割愛しますが、こちらも比較的お手軽にカレンダーを操作できました。
https://github.com/yuhong90/node-google-calendar

※本家のライブラリではコールバックだらけになりそうだったので今回は見送りました。

社内ツールのスクレイピングになるため、ソースは公開できないのですが
実行結果のイメージだけ共有したいと思います！

```
$ yarn start
yarn run v1.3.2
$ node-lambda run -f deploy.env
lambda is started
use proxy:******
success
 
xxx ログイン
loggedin done
<------------------------------>
終日            【セミナー】SYSxxx
14:30-15:00     相談 [施設A]
15:00-16:00     PF： v1.0.0 比率変更1%→20%
18:00-18:10     社内:daily-scrum
<------------------------------>
lambda will ended with success
Success:
"done success"
finally is started
lambda is closed
inserted event:...
```

# さいごに
PhantomJSでお手軽にブラウザ操作を自動化することで、
あまり頑張らずに、ルーチンワークをlambdaに代行させられるようになりました。

puppeteerやchromlessなど、さらにお手軽にできる情報がありましたら
ぜひ情報を教えていただけますと幸いです！


明日は @daichiii さんの記事です！