Python + AWS Lambda + Slackで定期的にパピヨンの画像を投稿する

[Advent Calendar 2015 Python その2](http://qiita.com/advent-calendar/2015/yet_another_python)の15日目の記事です．最近出社直後にパピヨン画像をSlackのパピヨン部屋に上げていたのですが，少し面倒になってきたので自動化してみました．

# 概要

社内の共通チャットであるSlackに定期的にパピヨン(犬)の画像を投稿します．
自分で画像を選んで投稿するのは面倒なので，一定期間置きに自動的に投稿できるような構成を考えました．

以下に簡単な構成図を書いておきます．

![python_advent_01.png](https://qiita-image-store.s3.amazonaws.com/0/43202/be31b17f-4865-8d16-9b19-a34e39429ccb.png)

(1) AWS Lambdaが定期的に起動しGCS APIからパピヨンの画像URLを複数を取得
(2) 複数画像の中から画像を選択
(3) 選択した画像URLをSlackに投稿

AWS Lambdaを使うことで，サーバレスで構成することができます．

# Google Custom Search API

Google Custom Search APIは，Googleのカスタム検索をコード上から実行できます．今回は，画像検索を使用します．
2015年11月までは，Google画像検索APIが使えたのですが現在利用できない状況となっているため，こちらを使います．
GCS APIは利用にAPI KEYが必要なのですが，無料プランでは一日の利用上限が1000回となっており多くはないのですが，今回の用途であれば問題ないので無料プランでKEYを取得します．

細かくは，[こちら](http://ryutamaki.hatenablog.com/entry/2014/01/18/171640)のブログで説明してありますので，よろしければ参照してください．

# Slack

Slackは，弊社のエンジニアのメインチャットとして利用しているサービスです．
インテグレーションには様々な方法が提供されていますが，POSTメッセージでチャットに投稿するための仕組みが存在しています．今回はその仕組み（Incoming Webhook）を使用してメッセージの投稿を行います．

## Incoming WebHookの設定

POSTメッセージの送り先となる投稿用のURLを取得する必要があります．
Slackの[Incoming WebHookの設定ページ](https://my.slack.com/services/new/incoming-webhook/)で投稿用URLを発行します。

チャンネルを選択して、`Add Incoming WebHooks Integration`ボタンをクリックします。
  ![スクリーンショット 2015-06-22 18.47.17.png](https://qiita-image-store.s3.amazonaws.com/0/43202/876ac261-fbe1-baae-6deb-ce4f257af1a7.png)


表示されるWebhook URLをコピーしておきます
  ![スクリーンショット 2015-06-22 18.48.30.png](https://qiita-image-store.s3.amazonaws.com/0/43202/0f05882e-0ba8-a324-b15b-7a4abc24cae7.png)

(オプショナル)必要な場合はデフォルトの投稿設定を変更できます。

# AWS Lambda

AWS Lambdaとは，実行可能なコードをアップロードすることで，サーバを用意することなくコードを実行できるサービスです．
Cronのようなスケージュリングタスクの実行やAPI Gatewayと組み合わせることでシンプルなAPIをサーバレスで実装することができます．
これまでは，node.js，javaのみが対応していましたが，2015年10月でpythonに対応しました．
今回はAWS Lambda Pythonを使用して実装してみます．

## スケジューリングタスクの登録

AWSからLambdaを選択し，新しくLambda Functionを作成します．
`blue printの選択`から`lambda-canary`を選択することで定期実行できるタスクを登録することができます．

![スクリーンショット 2015-12-13 17.59.44.png](https://qiita-image-store.s3.amazonaws.com/0/43202/558132fe-2fea-747a-8fed-69444b6d581f.png)

## スケジュールの登録

定期実行したいタスクのスケジュールを設定します．Schedule expressionでcronのように記述することができます．
cronによる指定の場合は，指定する時間がUTCになるため注意してください．
詳しくは[Lambda 関数を使用したスケジュールイベントの処理](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/getting-started-scheduled-events.html)を参照してください．

ここでは，5分置きに実行する設定にしています．

![スクリーンショット 2015-12-13 18.01.58.png](https://qiita-image-store.s3.amazonaws.com/0/43202/0a5ddc8f-2841-ecec-2178-c3a33bec1de1.png)


## タスクの設定

タスクの設定を行います．
基本はデフォルト設定として，以下のコードを貼付けます．
その後，前の手順で取得した`CUSTOM_SEARCH_API_KEY`と`CUSTOM_ENGINE_ID`，`SLACK_POST_URL`を設定してください．

```.python
#-*- coding:utf-8 -*-
from urlparse import urljoin
from urllib import urlencode
import urllib2 as urlrequest
import json
import random


# CUSTOM SEARCH API周りの設定
CUSTOM_SEARCH_API_KEY = "XXXXXXXXXXXXXXXXXXXXXXX"
CUSTOM_ENGINE_ID = "XXXXXXXXXXXXXXXXXXXXXXX"
URL_TEMPLATE = "https://www.googleapis.com/customsearch/v1?key={key}&cx={cx}&searchType=image&q={search_word}"

# SLACK周りの設定
SLACK_POST_URL = "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"


def post_image_to_slack(search_word):
    """
    google custom serachでimageのURLを取得し，ランダムでSLACKに投稿する
    """
    # urlを複数取得する
    urls = get_image_urls(search_word)

    # urlが取得できなかった場合は投稿しない
    if len(urls) == 0:
        return "no images were found."

    # urlをランダムに選択する
    url = random.choice(urls)

    # slack用のメッセージを作成
    post_msg = build_message(url)

    # slackに投稿
    return post(post_msg)


def get_image_urls(search_word):
    """
    GOOGLE CUSTOM SEARCH APIからキーワードで画像のURLを取得する
    """
    encoded_search_word = urlrequest.quote(search_word)
    url = URL_TEMPLATE.format(key=CUSTOM_SEARCH_API_KEY, cx=CUSTOM_ENGINE_ID, search_word=encoded_search_word)
    req = urlrequest.Request(url)
    res = urlrequest.build_opener(urlrequest.HTTPHandler()).open(req)
    data = json.load(res)

    if "items" not in data:
        return []

    links = []
    for item in data["items"]:
        links.append(item["link"])
    return links


def post(payload):
    """
    SlackにメッセージをPOSTする
    """
    payload_json = json.dumps(payload)
    data = urlencode({"payload": payload_json})
    req = urlrequest.Request(SLACK_POST_URL)
    response = urlrequest.build_opener(urlrequest.HTTPHandler()).open(req, data.encode('utf-8')).read()
    return response.decode('utf-8')


def build_message(url, **kwargs):
    """
    Slack用のメッセージを作成
    """
    post_message = {}
    post_message["text"] = url
    post_message.update(kwargs)
    return post_message


def lambda_handler(event, context):
    post_image_to_slack('パピヨン')
```

タスクが実行されると`def lambda_handler(event, context):`の部分が実行されます（設定で変更可能です）．
今回は静的にパピヨンと設定していますが，実行時にJSONを渡すことができるため引数として，検索パラメータを指定することも可能です．
また，実行されない場合は登録したスケジュールがDisableになっている場合があるため確認してみてください．

# 実行結果

設定した時間になると以下のような画像が投稿されています．

![スクリーンショット 2015-12-14 22.54.51.png](https://qiita-image-store.s3.amazonaws.com/0/43202/61b3af84-2fe8-e75c-a358-321dd94c429c.png)

良いパピヨンですね！

# 終わりに

AWS Lambda Pythonを使用することで，サーバを用意することなく，定期的にGoogleで画像検索を行いパピヨンの画像を投稿することができました．
さらにAPI Gatewayを使うことで対話的にパピヨンを投稿することができます．（Hubotを使ったほうがいいかもしれませんが，，）

皆さんもAWS Lambdaで良いPythonライフを！


# 参考記事

- http://dev.classmethod.jp/cloud/aws/aws-lambda-python-supported/
- https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/getting-started-scheduled-events.html
- http://qiita.com/tt2004d/items/50d79d1569c0ace118d6
- http://ryutamaki.hatenablog.com/entry/2014/01/18/171640