LoginSignup
16
16

More than 5 years have passed since last update.

Lambda,PythonでTwitterBotを運用する

Last updated at Posted at 2016-02-25

省略している箇所、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/

16
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
16