省略している箇所、S3の設定などがありますが
AWSLambdaでTwitBotを実装する手順です。
0.S3にあるJSONデータ
事前にS3にJSONデータがある前提です。
JSONデータはファイル名がyyyymmdd.json
構造は以下↓↓
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
virtualenv
これはpythonの仮想実行環境を指定ディレクトリにつくってくれます。
これによりモジュールを限定した環境で試すことが出来ます。python-lambda-local
これはローカル環境でlambdaのfunctionを実行するためのツールです。
実行にはevent.jsonというファイルを作成し、指定することでlambdaへの
仮の入力値を作成します。
以下コマンドで実行↓↓
python-lambda-local -f {lambda実行関数名} {lambda実行ファイル}.py ./event.json -t 5
最後のオプションtはlambdaのタイムアウト実行秒を指定(省略可)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を作ります。
- 利用する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
簡単に流れを説明します。
- _getTweetListでS3に接続し実行日のjsonファイルがあればその内容を取得
- ループ箇所で現在時刻と同じ時、分を指定しているデータを走査
- _getImagesでlinkで指定しているWEBページから画像をスクレイピングしダウンロード
- _uploadTweetImageでTwitterに画像をアップロード
- _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/