Edited at

Lambda,PythonでTwitterBotを運用する

More than 3 years have passed since last update.

省略している箇所、S3の設定などがありますが

AWSLambdaでTwitBotを実装する手順です。


0.S3にあるJSONデータ

事前にS3にJSONデータがある前提です。

JSONデータはファイル名がyyyymmdd.json

構造は以下↓↓

スクリーンショット 2016-02-25 10.27.11.png


1.開発環境の準備

まずは開発環境から。

Macの場合、ターミナルでの作業となります。

作業ディレクトリを作成したら(ここでは仮にlambda_function)

以下コマンドを順に実行します。

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


{

"name": "{Lambdaファンクションの名前}",
"description": "{説明}",
"region": "ap-northeast-1",
"handler": "{実行ファイル名(拡張子.py無し)}.{実行関数名}",
"role": "{roleのarnを指定}",
"timeout": 300,
"memory": 128
}

LambdaFunction実行時にそのFunctionの権限を指定するために事前にroleを作ります。


  1. 利用するpythonライブラリを記載します。
    requirements.txt

requests_oauthlib

beautifulsoup4
pytz


実行するPythonファイルを作成する

以下のファイルを作成します。

canary.py(ファイル名は任意)

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/