Lambda,PythonでTwitterBotを運用する

省略している箇所、S3の設定などがありますが
AWSLambdaでTwitBotを実装する手順です。

## 0.S3にあるJSONデータ

事前にS3にJSONデータがある前提です。
JSONデータはファイル名がyyyymmdd.json
構造は以下↓↓
![スクリーンショット 2016-02-25 10.27.11.png](https://qiita-image-store.s3.amazonaws.com/0/35271/e66a59cd-5395-c4cb-7c02-caadd96ffd55.png "スクリーンショット 2016-02-25 10.27.11.png")


## 1.開発環境の準備
まずは開発環境から。
Macの場合、ターミナルでの作業となります。

作業ディレクトリを作成したら(ここでは仮にlambda_function)
以下コマンドを順に実行します。
 
```bash
pip install virtualenv
source /{your path}/lambda_function/bin/activate

cd /{your path}/lambda_function/
pip install python-lambda-local
pip install lambda-uploader
```

1. virtualenv
これはpythonの仮想実行環境を指定ディレクトリにつくってくれます。
これによりモジュールを限定した環境で試すことが出来ます。

2. python-lambda-local
これはローカル環境でlambdaのfunctionを実行するためのツールです。
実行にはevent.jsonというファイルを作成し、指定することでlambdaへの
仮の入力値を作成します。
以下コマンドで実行↓↓
`python-lambda-local -f {lambda実行関数名} {lambda実行ファイル}.py ./event.json -t 5`
_最後のオプションtはlambdaのタイムアウト実行秒を指定（省略可）_

3. lambda-uploader
最後のlambdaへソースのアップロードを実行できます。
実行にはlambda.json,requirements.txtを作成し設定を記入します。
_lambda.json_


```json
{
    "name": "{Lambdaファンクションの名前}",
    "description": "{説明}",
    "region": "ap-northeast-1",
    "handler": "{実行ファイル名(拡張子.py無し)}.{実行関数名}",
    "role": "{roleのarnを指定}",
    "timeout": 300,
    "memory": 128
}
```
LambdaFunction実行時にそのFunctionの権限を指定するために事前にroleを作ります。

4. 利用するpythonライブラリを記載します。
_requirements.txt_

```
requests_oauthlib
beautifulsoup4
pytz
```

## 実行するPythonファイルを作成する
以下のファイルを作成します。
canary.py(ファイル名は任意)

```python
from boto3 import Session, resource
from requests_oauthlib import OAuth1Session
from bs4 import BeautifulSoup
import pytz 
from pprint import pprint
from datetime import datetime,timedelta
import urllib2
import random
import os.path
import urllib
import json

# Twitter API
CK = '{your twitter CK}'
CS = '{your twitter CS}'
AT = '{your twitter AT}'
AS = '{your twitter AS}'

TMP_DIR = '/tmp'

UPDATE_URL = 'https://api.twitter.com/1.1/statuses/update.json'
UPDATE_MEDIA = 'https://upload.twitter.com/1.1/media/upload.json'
IMAGES_SELECTOR = 'img'
IMAGES_NUM = 4

AWS_S3_BUCKET_NAME = "{* enter your backet name *}"
INTERVAL = 1

def _exists(bucket, key):
    return 'Contents' in Session().client('s3').list_objects(Prefix=key, Bucket=bucket)

def _getTweetList(keyName):
    if( _exists(AWS_S3_BUCKET_NAME, keyName) == False ):
        print("No JSON FILE"); return False

    s3 = resource('s3', region_name='ap-northeast-1')
    obj = s3.Bucket(AWS_S3_BUCKET_NAME).Object(keyName)

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

def _getImages(url):
    img_urls = []
    html = urllib2.urlopen(url)
    soup = BeautifulSoup(html, "html.parser")

    for img in soup.select(IMAGES_SELECTOR):
        img_urls.append(img['src']) 

    if len(img_urls) > IMAGES_NUM:
        fetch_urls = random.sample(img_urls, IMAGES_NUM)
    else:
        fetch_urls = img_urls

    filenames = []
    count = 1
    for img_url in fetch_urls:
        name, ext = os.path.splitext(img_url)
        filename = TMP_DIR+'/'+str(count)+ext
        urllib.urlretrieve(img_url, filename)
        filenames.append(filename)
        count = count+1

    return filenames

def _uploadTweetImage( images ):
    media_ids = []
    tw = OAuth1Session(CK, CS, AT, AS)

    for image in images:
        files = {"media": open(image, 'rb')}
        req_media = tw.post(UPDATE_MEDIA, files = files)
        if req_media.status_code == 200:
            media_ids.append(json.loads(req_media.text)['media_id'])
        else:
            media_ids.append(req_media.status_code)

    return media_ids

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

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

def _testAllFunction(event, context):
    ret = {}
    ret['getImages']        = _getImages("http://yahoo.co.jp")
    ret['uploadTweetImage'] = _uploadTweetImage([TMP_DIR+'/1.jpg', TMP_DIR+'/2.jpg', TMP_DIR+'/3.jpg', TMP_DIR+'/4.jpg'])
    ret['tweet']            = _tweet("Hello", [])
    ret['exists']           = _exists(AWS_S3_BUCKET_NAME, '20160209.json')
    ret['getTweetList']     = _getTweetList('20160209.json')

    return ret

def lambda_handler(event, context):
    ret = {}
    jst = pytz.timezone('Asia/Tokyo')
    jst_now = datetime.now(jst)

    today = jst_now.strftime("%Y%m%d")
    object_name = today + ".json"
    pprint(object_name)

    json_data = _getTweetList(object_name)

    if ( json_data != False ):
        tweets = json.loads(json_data)
        td_now = timedelta(hours=jst_now.hour, minutes=jst_now.minute)
        ret['main'] = [{'now': str(jst_now.hour)+':'+str(jst_now.minute)}]

        targetTweetList = []
        for tweet in tweets:
            td_tweet = timedelta(hours=tweet["hour"], minutes=tweet["minute"])
            if(td_now < td_tweet and (td_tweet - td_now).seconds/60 <= INTERVAL):
                pprint(tweet)
                targetTweetList.append( { "text" : tweet["text"], "link": tweet["link"] } )

        pprint(targetTweetList)
        for ttweet in targetTweetList:
            images = _getImages(ttweet["link"])
            media_ids = _uploadTweetImage(images)
            status = _tweet(ttweet["text"], media_ids)
            ret['main'].append(status)

    else:
        ret['main'] = "no data"

    return ret
```

簡単に流れを説明します。

1. _getTweetListでS3に接続し実行日のjsonファイルがあればその内容を取得
2. ループ箇所で現在時刻と同じ時、分を指定しているデータを走査
3. _getImagesでlinkで指定しているWEBページから画像をスクレイピングしダウンロード
4. _uploadTweetImageでTwitterに画像をアップロード
5. _tweetでTweetを実行します

上記ファイルを作成したらまずはローカルで試すためにライブラリをインストールします。

```
pip install requests_oauthlib
pip install beautifulsoup4
pip install pytz
pip install boto3
```

ローカルで確認。
`python-lambda-local -f lambda_handler canary.py event.json`

エラーが出なければOKです。

## Lambdaにアップロードする
lambda.jsonに以下を記入

```
{
    "name": "Canary",
    "description": "sugoroku schedule tweet",
    "region": "ap-northeast-1",
    "handler": "canary.lambda_handler",
    "role": "{roleのarnを指定}",
    "timeout": 300,
    "memory": 128
}
```

requirements.txtは既に作っているので以下実行↓
`python-uploader`

うまくいけばAWSマネジメントコンソール上のLambdaFunctionにCanaryと追加されているはずです。

## cron設定
とりあえずマネジメントコンソールからTestボタンを実行してLambda上での実行を確認してみます。
問題なければcronの設定に入ります。

### cronの設定の仕方
Evnt sources > Add event sourceから追加

* Scheduled
* Scheduleイベントは最短で5分サイクルなので5分に1回を5つ追加することで1分に1回を実現します

後は実際にjsonファイルに予約投稿を入れてみて動作を確認してください。

ちなみにこんかいは触れませんでしたがPythonを触れてみた感想としては

* 文字コードに気を使わなければならない
* ローカルタイムゾーンの設定が少し面倒
* Lambda上では動かないライブラリがあり、そこに気づくのに時間がかかった

上記のような点で苦労しましたが、概ね触りやすい印象でした。

##Lambda,Python,TwitterAPI参考リンク

Python で画像付きツイート - Qiita
http://qiita.com/yubais/items/864eedc8dccd7adaea5d

---

Twitter retrobot 構築 で学ぶ AWS Lambda Python - Qiita
http://qiita.com/ketancho/items/6d5137b48d94eced401e

---
GAS+S3を使ってサービス運営向け設定ツールを超絶簡単に作成する方法 - Qiita
http://qiita.com/hirokidaichi/items/769e330284302a799095

---

Lambda ｜ 特集カテゴリー ｜ Developers.IO
http://dev.classmethod.jp/referencecat/aws-lambda/

---

AWS Lambda Pythonをlambda-uploaderでデプロイ ｜ Developers.IO
http://dev.classmethod.jp/cloud/deploy-aws-lambda-python-with-lambda-uploader/

---

AWS Lambda Pythonをローカル環境で実行 ｜ Developers.IO
http://dev.classmethod.jp/cloud/aws/invoke-aws-lambda-python-locally/