AWS Lambdaでheadless-chromeを動かしてRedashのグラフをキャプチャしてSlackに投稿する


この記事はニフティグループ Advent Calendar 2018の5日目の記事です。
4日目は@motaHackさんの[MongoDBとmetabaseでjsonファイルを見やすくしたい 後編](https://qiita.com/motaHack/items/49d45c07c61f286851ec)でした。

# はじめに
　弊社でもデータ可視化のためにRedashを使っていますが、[Redash本家](https://redash.io/)で提供されているslack botが大人の事情で使えない（使いづらい）ため、AWS Lambdaでheadless-chromeを動かしてRedashのグラフを画面キャプチャしSlack APIで投げ込むことにしました。（Redash用にサーバーあるならそこでbot動かせばいいという話もありますが...)

# 下準備

## Slack APIのTokenを準備する
 先人の知恵が沢山あるので説明は割愛しますが、今回はファイルをアップロードしますのでスコープは[files.upload](https://api.slack.com/methods/files.upload)が利用できるものにする必要があります。

参考1:[Slack API 推奨Tokenについて](https://qiita.com/ykhirao/items/3b19ee6a1458cfb4ba21)
参考2:[OAuth Permission scopes](https://api.slack.com/docs/oauth-scopes)

## AWS周りの設定をする
 RedashサーバーがLambdaと同じsubnet上にあるとかアクセスしやすい所にあるのであれば特にやることはありません。大人の事情でRedashへのアクセスがグローバルIPアドレスからになる場合は、IPアドレスでのアクセス制限がかけられるようにElastic IP、VPCにNATゲートウェイとインタネットゲートウェイの設定をした方がよいと思います。

参考：[VPC Lambda関数にインターネットアクセス権限を付与するにはどうすればよいですか？](https://aws.amazon.com/jp/premiumsupport/knowledge-center/internet-access-lambda-function/)

![aws.png](https://qiita-image-store.s3.amazonaws.com/0/218020/0d9456d8-d2c5-0632-c7b8-45955d89e430.png)

## Lambdaに組み込むパッケージなどを展開する環境を準備する
 Linuxな環境であれば何でも大丈夫ですが、エディタに拘りがなければ[AWS Cloud9](https://aws.amazon.com/jp/cloud9/)が楽だとおもいます。（周りで使っている人を見かけませんが）
この記事では、細かいdeployのやり方などは割愛します。

# 導入
## headless-chromeを用意する
　Lambdaにアップロードできるファイルサイズには制限があるのでできるだけコンパクトにしておく必要がありますが、自力でやるのは大変なので先人が作成してgithubで公開しているもの(serverless-chrome)を使用します。pythonの場合は動かないバージョンがあったりするため注意が必要です（[参考](https://github.com/adieuadieu/serverless-chrome/issues/133))

ダウンロードできたら、binディレクトリを作って展開しておきます。

```
$ wget https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-37/stable-headless-chromium-amazonlinux-2017-03.zip
$ mkdir bin
$ unzip stable-headless-chromium-amazonlinux-2017-03.zip -d bin/
$ rm stable-headless-chromium-amazonlinux-2017-03.zip
```

その他にchromedriver、Seleniumも必要なので同じディレクトリに展開しておきます。
chromedriverはchromeのバージョンにあったものを使う必要がありますので、serverless-chromeのv1.0.0-37ならchromium 64.0.3282.167 (stable channel) 相当ということでchromedriver 2.37をダウンロードします。

```
$ wget https://chromedriver.storage.googleapis.com/2.37/chromedriver_linux64.zip
$ unzip chromedriver_linux64.zip -d bin/
$ rm chromedriver_linux64.zip
```

Selenium(python)はpipでインストールします。（-tでインストール先ディレクトリの指定を忘れないように）

```
$ pip install selenium -t .
```

必須ではありませんが、日本語フォントを入れないと日本語が豆腐化しますのでIPAフォントなどを入れておくとよいと思います。
.fontsディレクトリを作成してその下にフォントファイルを置いておくと反映されます。

```
$ wget https://oscdl.ipa.go.jp/IPAexfont/IPAexfont00301.zip
$ unzip IPAexfont00301.zip
$ mkdir .fonts
$ cp IPAexfont00301/*.ttf .fonts/
$ rm -f -r IPAexfont00301*
```

## Lambda Functionを書く
pythonでは以下のようになります。

```python:lambda_handler.py
from selenium import webdriver
import time
import os

os.environ['HOME'] = '/var/task'

SLACK_CHANNEL = 'xxxxxxxxxxxx'
SLACK_TOKEN = 'xoxb-000000000000000000000-xxxxxxxxxxxxxxxxxxxxx'
# グラフのURLは埋め込み用iframeを表示するリンクから確認する
REDASH_URL = "https://redash.xxxx/query/xxx/visualization/xxxx?api_key=xxxxx"

def lambda_handler(event, context):
    options = webdriver.ChromeOptions()
    options.binary_location = "./bin/headless-chromium"
    options.add_argument("--headless")
    options.add_argument("--disable-gpu")
    options.add_argument("--window-size=1920,1080")
    options.add_argument("--single-process")
    options.add_argument("--ignore-certificate-errors")
    options.add_argument("--homedir=/tmp")

    driver = webdriver.Chrome("./bin/chromedriver",chrome_options=options)
    driver.get(REDASH_URL)
    time.sleep(5) # 読み込みが遅いページはsleepで時間を稼ぐ
    driver.save_screenshot('/tmp/screenshot.png')

    driver.close()

    # slackに投稿
    files = {'file' : open('/tmp/screenshot.png','rb')}
    params = {
        'token' : SLACK_TOKEN,
        'channels': SLACK_CHANNEL,
        'filename' : 'redashGraph',
        'initial_comment': 'グラフのキャプチャですよ',
        'title' : 'Redashのグラフ'
    }

    requests.post(url='https://slack.com/api/files.upload', params=params, files=files)

```

# 注意点
 - Lambdaの上限メモリが少ないと処理が終わるまでに時間がかかったりします(デフォルトの128MBだとつらめ)
 - プログラム中で環境変数HOMEに/var/taskを設定しているところがありますが、この設定をしないと日本語フォントが反映されません
 - Lambda実行時にheadless-chromeがファイルを作ったりしますので、自由に書き込めるディレクトリ/tmpをhomedirとして指定しておく必要があります
 - sleepを入れているところがありますが、コメントの通りsleepしないとJavaScriptなどで非同期に読み込んでいたり、読み込み自体が遅いページではキャプチャされない(真っ白になる)ことがあります(処理時間はできるだけ短くしたいところですが...)

上の例のようにRedashグラフのURLとかslackのtokenなどを埋め込むとメンテが辛いので、Lambdaの環境変数にするなり設定を別の場所に持つなりした方がいいと思いますが、何かの参考になれば幸いです。

次は@hicka04さんの「社内でSwift勉強会を開いた話」です。