Twitter retrobot 構築 で学ぶ AWS Lambda Python


Lambda の Advent Calender にタッチの差で参加できなかったので、#0的に11月30日を狙って（勝手に）投稿してみます!!

今年の頭から EC2 上で Twitter retrobot を動かしていたのですが、先月 Lambda が Python 対応＆スケジュール実行されたので、今回その retrobot を Lambda に移行してみました。

最初は Lambda の勉強のために移行しようと思ったのですが、移行後金額を見積もってみるとまさかの無料枠内（しかもかなり余裕があるように見えます。）に収まったので、経費削減にもつながりました。（計算間違ってるかもしれないので、最後に見積もりを載せてみました。間違っていたらご指摘ください！）

### 前提／環境

* 10分に1回、約1年前のツイートをつぶやきなおすbotになります。
* Lambda 歴トータル1週間くらいです。（4月のハンズオン以来。。）
* Python も初心者です。（運用スクリプト少し書いたことある程度）
* Yosemite 10.10.4 で作業しています。

### こんな人向け
* Lambda function を初めて書く方
* 安価に retrobot 運用してみたい方


## Twitter アプリの登録とアクセスキーの取得

[こちらのページ](http://website-planner.com/twitter%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E4%BD%9C%E6%88%90%EF%BC%88consumer-key%E3%80%81consumer-secret%E3%80%81access-token%E3%80%81access-token-secret/)を参考にして進めてください。

Consumer Key（CK）
Consumer Secret（CS）
Access Token（AT）
Access Token Secret（AS）
の4つが必要になります。

## 過去1年分のツイートファイル(json形式/月ごと)を取得し、S3にアップロードする

過去のツイートは tweet.zip という形でダウンロードできます。
設定画面を開いてください。

![スクリーンショット 2015-11-30 22.05.54.png](https://qiita-image-store.s3.amazonaws.com/0/52494/662445a5-615a-7a3a-6cc6-d7e4442eab03.png "スクリーンショット 2015-11-30 22.05.54.png")

設定画面の一番下に「全ツイート履歴」という項目がありますので、そこからメールを送信してください。
（私は最近履歴をダウンロードしているので「再送信」となっていますが、みなさんの画面では「送信」になっているかと思います。）

![スクリーンショット 2015-11-30 22.08.52.png](https://qiita-image-store.s3.amazonaws.com/0/52494/a105a2d4-06f4-8500-aab3-d2c3526cf564.png "スクリーンショット 2015-11-30 22.08.52.png")

次に、S3 に retrobot 用のバケットを作成してください。
S3 > Create Bucket で作成することができます。
名前は全世界でユニークにしないとなりませんので、(twitterアカウント名).retro とでもすればよいと思います。

そして、先ほどのzipファイルを展開し、直近1年分の js ファイルを upload してください。
こんな感じになるはずです。↓

![スクリーンショット 2015-11-30 22.14.15.png](https://qiita-image-store.s3.amazonaws.com/0/52494/77a17abf-0cf1-930b-98ed-f96c48ceccc4.png "スクリーンショット 2015-11-30 22.14.15.png")



## Pythonスクリプトの作成

やっていることは下記の通りです。
(Python 素人なのでもっといい書き方あればぜひコメントください！)

* 1年前同月のツイートファイルを S3 から取得
* 1年前の同じ時刻〜10分後までのツイートをフィルタする
* フィルタされた結果をつぶやく

` # * enter your hogehoge` となっている5箇所だけ変更すれば動くはずです。

```python:ketancho_retro.py
from requests_oauthlib import OAuth1Session
import commands, boto3, re, json

CK = '***' # * enter your Consumer Key *
CS = '***' # * enter your Consumer Secret *
AT = '***' # * enter your Access Token *
AS = '***' # * enter your Accesss Token Secert *

UPDATE_URL = 'https://api.twitter.com/1.1/statuses/update.json'

AWS_S3_BUCKET_NAME = "***" # * enter your backet name *

def _(cmd):
    return commands.getoutput(cmd)

# get "keyName" obj from S3
def _getTweetList(keyName):
    s3 = boto3.resource('s3', region_name='ap-northeast-1')
    bucket = s3.Bucket(AWS_S3_BUCKET_NAME)
    obj = bucket.Object(keyName)

    response = obj.get()
    body = response['Body'].read()
    return body.decode('utf-8')

# tweet text
def _tweet(text):
    params = {"status": text }
    twitter = OAuth1Session(CK, CS, AT, AS)
    req = twitter.post(UPDATE_URL, params = params)

    if req.status_code == 200:
        return text
    else:   
        return req.status_code

def handler(event, context):
    # yyyy_mm.js
    GET_OBJECT_KEY_NAME = _('date +"%Y_%m" --date "1 year ago"') + ".js"
    tweetList = _getTweetList(GET_OBJECT_KEY_NAME)

    # delete first line ("Grailbird.data.tweets_yyyy_mm =")
    tweetListJson = re.sub(r"^.*\n", "", tweetList)
    tweets = json.loads(tweetListJson)
    
    # yyyy-mm-dd hh:m
    targetDateTime = _('date +"%Y-%m-%d %H:%M" --date "1 year ago"')[:-1]
    targetTweetList = []
    for tweet in tweets:
        if tweet["created_at"].startswith(targetDateTime):
            # replace mention and hashtag mark
            text = tweet["text"].replace('@','*').replace('#','+')
            targetTweetList.append(text)

    # desc -> asc
    targetTweetList.reverse()

    for text in targetTweetList:
        _tweet(text)
```

## デプロイパッケージの作成

外部ライブラリを使用しているため、zipファイルにまとめてアップロードする必要があります。
※ここでは手順のみ記載しますが、こちらに詳細がありますので、詳しく知りたい方はご確認ください。
→https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html

Lambda ファンクション用のディレクトリを作成してください。
また、今回 requests_oauthlib モジュールを使用しますので、こちらをインストールしてください。

```
mkdir lambda_function
cd lambda_function/
pip install requests_oauthlib -t ./
```

pip がなければインストールしてください。（sudo 権限が必要です）

```
easy_install pip
```

Python ファイルのアップロード方法は下記の3つがありますが、

* ソースを Management Console 上に記入する
* zip ファイルを作成し直接 upload する
* zip ファイルを作成し、S3 に upload し、Lambda function から参照する

今回のように外部モジュールを使用するときは、zip ファイルを作成する必要があります。
今回は2番目の方法で進めます。

```
cp (pyのあるdir)/ketancho_retro.py ./
zip -r ketancho_retro.zip ./
```



## Lambda ファンクションの作成

Lambda のトップ画面から Create Lambda function を選択します。

![スクリーンショット 2015-11-28 19.57.35.png](https://qiita-image-store.s3.amazonaws.com/0/52494/6b8a1d3c-4f47-3e53-3b7e-346222e07c6c.png "スクリーンショット 2015-11-28 19.57.35.png")


blue print は hello-world-python を選択してください。

![スクリーンショット 2015-11-28 20.01.04.png](https://qiita-image-store.s3.amazonaws.com/0/52494/5ec265b1-5999-48e5-1e8b-99efd309dde4.png "スクリーンショット 2015-11-28 20.01.04.png")

Configure function では、下記の項目を設定してください。

* Name：Lambda function の名前を入力してください。
* Lambda function code："Upload a .ZIP file" を選択し、先ほど作成した zip ファイルを選択してください。
* handler：[先ほど作成した .py のファイル名].[最初に実行するメソッド名]になります。私の場合は、ketancho_retro.handler となります。
* Role：今回は S3 バケットにアクセスが必要なので、Create New Role から "S3 execution role" を選択してください。すると、別タブで IAM 画面が開かれますが、何も変更せずAllowとしてください。元の画面に戻ってきて、今作成したロールが設定されます。

![スクリーンショット 2015-11-28 20.07.15.png](https://qiita-image-store.s3.amazonaws.com/0/52494/e373a5ee-d7a9-dc10-243d-5b3a5db90125.png "スクリーンショット 2015-11-28 20.07.15.png")

最後にAdvanced sttingsですが、タイムアウト値が3秒ではたまにタイムアウトしてしまうので、10秒としています。
設定できたらNextを押してください。

![スクリーンショット 2015-11-28 20.16.45.png](https://qiita-image-store.s3.amazonaws.com/0/52494/4019f2d3-ec0c-b171-e8ff-dcd1efb6e9ac.png "スクリーンショット 2015-11-28 20.16.45.png")


## Event sources の設定

Lambda ファンクションが作成できたら、次に Event sources を設定します。
何をトリガーにファンクションを実行するかの定義になります。
「Event source」タブを押してください。

![スクリーンショット 2015-11-30 22.30.08.png](https://qiita-image-store.s3.amazonaws.com/0/52494/88a65e2d-93e8-5caa-17a1-e58b34a8b03f.png "スクリーンショット 2015-11-30 22.30.08.png")

「Add event source」を押してください。 

![スクリーンショット 2015-11-30 22.33.41.png](https://qiita-image-store.s3.amazonaws.com/0/52494/53774f59-03c0-8a95-13ca-2a3df10ea5c5.png "スクリーンショット 2015-11-30 22.33.41.png")

今回はcronライクに10分に1回起動するようにしたいので、
「Event source type」は Scheduled Event を、
「Schedule expression」は cron(0/10 * ? * * *) としてください。

![スクリーンショット 2015-11-30 22.36.03.png](https://qiita-image-store.s3.amazonaws.com/0/52494/14003769-156b-e279-17ab-5026cae036c2.png "スクリーンショット 2015-11-30 22.36.03.png")

ここまでで設定完了です。
うまく動くかテストしたい場合は、「Test」ボタンからテストできます。
インプットは何にするか聞かれますが、そのまま Save and Test としてください。
（このインプットは使わないのでなんでもOKです。）
ただし、１年前の同じ時間から10分後までに何もつぶやいていない場合は、botの方も何もつぶやかれません。
すぐにテストしたい場合は、`targetDateTime `を適当な値に変更してzipファイルをアップロードしなおしてテストしてみてください。

## 見積もり

最初に「タダかも！」と書きましたが、私は下記のように見積もっています。
[AWS公式ページ](https://aws.amazon.com/jp/lambda/pricing/)に「Lambda では 1 か月に 1,000,000 件の無料リクエストおよび 400,000 GB/秒のコンピューティング時間が無料利用枠となっています。 」と書かれています。

まず、リクスト数ですが、
10分に1回 x 6 x 24時間 x 31日分で最大でも4,464回/月となり、無料枠の1%にも満たないです。

次に、コンピューティング時間ですが、何日か運用したのですが、
![スクリーンショット 2015-11-30 22.56.00.png](https://qiita-image-store.s3.amazonaws.com/0/52494/e0b2bd1b-4bce-e6bf-3640-4df2ab25509a.png "スクリーンショット 2015-11-30 22.56.00.png")
のように、せいぜい3,000msで処理が完了します。（青がMax、緑がavg、橙がminです。）
これを月換算（x4,464回分）すると、13,392秒/月になります。
Lambdaでは何GBメモリおを占有したかで決まるので、これに設定しているメモリ128MBをかけると1,674GB、、無料枠のこちらも1%にも届きません。
なのでタダ！と書いた次第です！（間違っていてら教えてください。）

## まとめ

retrobot は Lambda を学ぶ材料として、ちょうどいい規模だと思います。
スムーズにいけば１時間程度で完了すると思いますので、サーバレスこと始めとしてやってみてもらえればと思います。
次は、AMI の自動取得を Lambda に移行しようと思います。

また、retrobot は1年前に自分がやろうとしてたこと/やりたかったこと、でもやってなかったことを思い出すのにちょうどいいです。
タダで運用できそうですので、備忘録がてら（？）運用してみてください！