AWS LambdaでCasperJSを実行してファイルアップロードを自動化する
AWS上のデータを別サービスに連携するために、AWS LambdaからCasperJSを使ってファイル配置を自動化する仕組みを作ってみました。 APIでデータをPOSTできれば簡単なのですが、今回はGUI上からファイルをアップロードしないといけないため、技術の無駄遣いをしてみます。
日次でファイルをアップロードしたい
ことの発端は以前書いた記事 「クロスアカウントで共有されたS3バケットはAWSコンソール上から参照可能なのか」 にて、 S3のバケット共有の機能を使ってファイルの提供をしようと試みた のですが、社内のセキュリティ統制的にNGを喰らってしまいましたので、 指定のファイルストレージサービスを経由してファイルの授受を行う必要が出てきました。
そのファイルストレージサービスというのが若干レガシーなシステムで、APIを使ったファイルのアップロードができません。 そのため、ヘッドレスブラウザでのGUI操作ができるライブラリを使用してファイルアップロードをしようと考えた次第です。
これを実装しないと、私が毎朝システムにログインしてファイルをアップロードするという苦行が発生するため、是が非でも作る必要がありました。
退屈なことはプログラムにやらせましょう。
CasperJSとは
CasperJS は PhantomJS のラッパーライブラリです。 PhantomJS 自体はwebkitをベースとしたヘッドレスブラウザです。 実ブラウザを起動するSeleniumよりも高速に動作するので、GUIを持つ必要のない処理(例えばGUIの自動テストや、今回のような機械的な処理)に向いています。 昨年頃に Chromeもヘッドレスで起動できる ようになっているため、 PhantomJSでなくても良いのですが、過去にPhantomJSを使った経験があったため、再びこれを採用しています。
ラッパーであるCasperJSを使う利点は、ブラウザ操作のユーティリティが揃っていることです。 セレクタに対するwaitや、イベントの発火、データ入力等のコードをシンプルに書くことができます。
というか、PhantomJS単体だと自前定義のfunctionが多くなるためオススメできません。
Lambda + CasperJS で実現してみよう
早速実装してみましょう。今回作成したプログラムは こちら です。 なお、このプログラム自体は node-casperjs-aws-lambda を参考にしています。
今回実装したアーキテクチャはざっくり以下のようなイメージです。
実行環境やライブラリは以下になります。
- Node 6.10
- CasperJS 1.1.4
- PhantomJS 2.1.1
また、処理の流れは以下になります。
- LambdaがETL処理をする
- ETL処理終了後にS3にファイルをアップロード
- BucketのPUTイベントを基にファイルアップロード用のLambdaが実行される
- LambdaでCasperJSが動き、他サービスにファイルをアップロードする
実装時のポイント
Lambdaに割り当てるリソースは大きめに、タイムアウトは長く設定する
ヘッドレスとは言え、CasperJSを実行するために、Lambdaに割り当てるメモリは大きめにした方が良いです。
加えて、Lambdaのタイムアウト値は最大値の5分に設定しておきましょう。もちろん、これらは実装する処理の重さに依存します。
AWS LambdaにPhantomJSのパスを通す
node_module
内のPhantomJSはLambda上ではいい感じに見てくれなかったので、PhantomJSのバイナリをダウンロードして直接パスを通してあげました。
余談ですが、AWS Lambdaの環境変数一覧は ここ に纏められています。はじめて知りました。
S3オブジェクトを一度Lambdaのコンテナ上にダウンロードする
S3のPUTイベントをトリガーに処理が実行されるのですが、event
オブジェクトにはバケットの情報やオブジェクトキーの情報しかないため、
そこから一度、Lambdaコンテナの /tmp/
配下とかに一度ダウンロードします。
Lambda実行の最後にS3バケットへ画面キャプチャをアップロードする
CasperJSのデバッグはコンソールに情報を出力するよりも caputure
関数を呼び出して、その瞬間の画面キャプチャを取得する方が捗ります。
ただ、Lambda上で実行している場合には、Lambdaの処理が終了するとコンテナも終了するため、処理の最後に任意のS3にアップロードしてあげます。
なお、キャプチャファイルはカレントディレクトリ( /var/task
配下)に出力しようとすると、書き込み権限がないエラーになってしまいましたので、 /tmp/
に吐き出しています。
また、キャプチャーした画像ファイルは大抵複数できあがるので、aws-sdk
でS3アップロードする処理は Promisse
を使って書きました。
ブラウザの言語設定を英語にした
CasperJSで操作するWebコンテンツがi18n対応されていたため、ヘッドレスブラウザの設定を英語にしました。 CSSセレクタではなくてエレメント内のテキスト情報で要素を引きたい ことが発生した場合に、マルチバイト文字だと引っかからなかったからです。
1// Change browser lang
2casper.on('started', function () {
3 this.page.customHeaders = {
4 "Accept-Language": "en-US"
5 }
6});
ランタイムに依存しない という意味でも設定しておいた方が良いと思います。
まとめ
今回はAWS Lambda上でCasperJSを実行することで、S3上のファイルを別のストレージサービスへアップロードすることができました。 CasperJSからPhantomJSを呼び出せるようにPATHに追加したり、キャプチャ画像をアップロードするためのバケットを準備したり、 下準備に若干時間がかかりますが、一度作れば動きが理解できると思います。
Lambdaのタイムアウト時間の最大値である5分以内に処理を終了させる必要があるため、最初はタイムアウトしないか心配でした。 しかし、メモリも大きめに割り当てて上げると実行自体は数十秒くらいで終了したので、ヘッドレスブラウザ最強です。 CasperJSのコードも短かったのが良かったのかもしれません。
と、これを書きながら、「CasperJSのコードを複数のLambdaで分担してGUIのテストできたらかっこいいな」と感じました。