【Python/AWS】第1回 LambdaとOpenWeatherMapで天気予報を自動ツイートする【データ分析】

# 1. はじめに
本記事は【Python/AWS】の第1回として、
AWSのサービスを利用した一連のデータ分析の初回となります。

私自身、AWSのサービスを触るのは初めてなので、
細かい説明など不足する点はあるかと思いますが、
大まかな流れをこちらで解説していきたいと思います。

5回ほどに分けて投稿していく予定ですが、
分析の全体像は以下のようになります。
![AWSアーキテクチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/dfc7f859-8e02-ce11-59ed-5cfa4efd8315.png)
全体の分析目的は前回の分析から継続/発展して、
**<font color="red">「Splatoon3」におけるコンテンツのユーザー満足度調査</font>** です。

収集したデータから項目ごとに感情分析を行い、
ポジティブ/ネガティブの値を可視化することが最終目的です。

**<font color="blue">▼前回の記事はこちら▼</font>**

https://qiita.com/kawanago_py/items/38fbf30c3e1d1580c76c

## 1-1. 本記事の概要と目標

初回のテーマは「AWS Lambdaの活用」ということで、
前回トピック分析でも利用したTwitterAPIと組み合わせ、
天気予報を自動ツイートしてくれる仕組みを作ります。

今回作成するパートのイメージはこのような形です。
![天気予報アーキテクチャ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/3e1015e0-435c-4b36-14e7-e17c8c91c48d.png)

使用するサービスとそれぞれの役割は以下の通りです。
**Aystem Maneger：** TwitterAPIのkeyなどを安全に保管するために使用
**Watch Cloud：** Lambdaを実行するタイミングをコントロールするために使用
**AWS Lambda：** Pythonスクリプトを実行するためのサービス
**OpenWeatherMap：** 天気に関わる情報を取得できるAPI(AWSサービスではない)

なお本記事ではAWSの登録方法や、
サービス利用に必要な初期設定は取り上げていません。
必要に応じて以下の記事を参考にして下さい。

**<font color="blue">▼AWSへの登録/IAMロールの作成▼</font>**

https://aws.amazon.com/jp/register-flow/

https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-1st-Step-2022-reg-event.html?trk=aws_introduction_page

## 1-2. 自動実行の流れ
自動実行の順番、流れとしては以下の通りです。
**１．Watch Cloudにて毎朝8:00にトリガー
２．Lambdaのスクリプト内で以下を実行
３．System Managerで保管していたTwitterAPIを取得
４．OpenWeatherMapから天気情報を取得
５．取得した天気情報を文章に含めて自動ツイート**

# 2. 実装
大まかなと説明の流れとして、
**１．TwitterAPIのkeyをssmに保管**
**２．Lambda_functionの作成**
**３．Lambdaへのデプロイ**
**４．テスト**
**５．cloud watchの設定**
の5段階に分けて確認をしていきます。

## 2-1.ssmにパラメータを保管

![ssm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/b112d274-5e00-07d8-1962-9723b80bd742.png)
TwitterAPIのkeyやtokenは情報漏れが怖いため、
<font color="red">"Systems Manager"</font>の<font color="red">"parameter store"</font>を使用します。

なお自分がparameter storeを使用した際、
bearer_tokenの文字列が長すぎたためか、
全てを一つにまとめるとうまくいきませんでした。

そのためbearer_tokenのみ、単体で保管しているという状況です。

**<font color="blue">▼ssm利用時に参考にしたリンク▼</font>**

https://aws.amazon.com/jp/builders-flash/202203/aws-drill-twitter-bot-3/?awsf.filter-name=*all#03-01

## 2-2.Lambda_functionの作成
まずは以下がコードの全体像となっています。

``` Lambda_function.py
import sys
# tweepyをインストールしたディレクトリ相対パスを指定する
sys.path.append('./python_package')
# coding: UTF-8
import json
import tweepy
import boto3
import datetime
from pyowm.owm import OWM
from pyowm.utils.config import get_default_config
from statistics import mode

ssm = boto3.client('ssm', "ap-northeast-1")
weekday_dict = {0:"月曜日", 1:"火曜日", 2:"水曜日", 3:"木曜日", 4:"金曜日", 5:"土曜日", 6:"日曜日"}

def lambda_handler(event, context):
    response = ssm.get_parameters(Names=["/credentials/twitter"], WithDecryption=True)
    params = json.loads(response['Parameters'][0]['Value'])
    bearer_res = ssm.get_parameters(Names=["bearer_token"], WithDecryption=True)
                                        
    client = tweepy.Client(consumer_key = params["consumer_key"],
                           consumer_secret = params["consumer_secret"],
                           access_token = params["access_token"],
                           access_token_secret = params["access_token_secret"],
                           bearer_token = bearer_res["Parameters"][0]["Value"]) #文字列が長いとjsonファイルの読み込みでエラーになるため
                           
    t_delta = datetime.timedelta(hours=9)
    JTS = datetime.timezone(t_delta, "JTS")
    now = datetime.datetime.now(JTS)
    weekd = weekday_dict[datetime.datetime.weekday(now)]
    today = now.strftime("%m/%d") + "(" + weekd[0] + ")"
    
    config_dict = get_default_config()
    config_dict["language"] = "ja"

    owm = OWM("自分のAPIkey", config_dict)
    mgr = owm.weather_manager()

    w = mgr.weather_at_place("Tokyo,JP").weather
    temp = w.temperature("celsius")["temp_max"]
    
    forecast = mgr.forecast_at_place("Tokyo,JP", "3h").forecast
    status_list = []
    for weather in forecast.weathers[2:7]:
        status_list.append(weather.detailed_status)
    today_w = mode(status_list)

    tweet_text = "{}\n天気は{}予報で、最高気温予想は{}だそうです。\n今日も一日頑張りましょう。(from lambda)".format(today,today_w, temp)
    client.create_tweet(text=tweet_text)
```

天気予報をツイートするだけなので
とても短い簡単なコードになっています。

コードの流れとしては
**１．ssmからtwtterAPIのキーを取得
２．OpenWeatherMapから天気予報情報を取得
３．定型文に取得した情報を含めてツイート**
といった具合になっています。

またLambdaを使用する際のルールとして、
`lambda_hundler`を必ず定義する必要があります。

これはLambdaが実行される際に最初に実行される関数です。
コードはこの中に含めて書いていきます。

### 2-2-1.ssmからキーを取得
``` get_from_ssm.py
ssm = boto3.client('ssm', "ap-northeast-1")

response = ssm.get_parameters(Names=["/credentials/twitter"], WithDecryption=True)
params = json.loads(response['Parameters'][0]['Value'])
bearer_res = ssm.get_parameters(Names=["bearer_token"], WithDecryption=True)
                                        
client = tweepy.Client(consumer_key = params["consumer_key"],
                       consumer_secret = params["consumer_secret"],
                       access_token = params["access_token"],
                       access_token_secret = params["access_token_secret"],
                       bearer_token = bearer_res["Parameters"][0]["Value"])
```
ssmに保管したkey/tokenを取得します。

`boto3.client("ssm", "region")`にてクライアントを準備、
`get_parameters(Nmes=["*パラメータ名*"])`にて取得できます。

中身はJSONファイル形式なのでjsonで読み込み、
そのまま`tweepy.Client`の引数に指定しています。

**<font color="blue">▼SSM(boto3)のドキュメントはこちら▼</font>**

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html

### 2-2-2.OpenWeatherMapから天気情報を取得

``` OpenWeatherMap.py
config_dict = get_default_config()
config_dict["language"] = "ja"

owm = OWM("*自分のAPIkey*", config_dict)
mgr = owm.weather_manager()

w = mgr.weather_at_place("Tokyo,JP").weather
temp = w.temperature("celsius")["temp_max"]
    
forecast = mgr.forecast_at_place("Tokyo,JP", "3h").forecast
status_list = []
for weather in forecast.weathers[2:7]:
    status_list.append(weather.detailed_status)
today_w = mode(status_list)
```
今回OpenWeatherMapの詳細については省き、
大まかな説明とさせて頂きます。

ここで行っていることは以下の2点です。
**・最高気温予測の取得
・日中の天気予報情報の取得**

無料のAPIでは3時間ごとの予報が取得可能のため、
日中の天気予報で最も多かった予報を採用しています。

**<font color="blue">▼参考ページ▼</font>**

https://openweathermap.org/

https://pyowm.readthedocs.io/en/latest/v3/code-recipes.html

### 2-2-3.取得した情報をツイート

``` tweet.py
weekday_dict = {0:"月曜日", 1:"火曜日", 2:"水曜日",
                3:"木曜日", 4:"金曜日", 5:"土曜日", 6:"日曜日"}

t_delta = datetime.timedelta(hours=9)
JTS = datetime.timezone(t_delta, "JTS")
now = datetime.datetime.now(JTS)
weekd = weekday_dict[datetime.datetime.weekday(now)]
today = now.strftime("%m/%d") + "(" + weekd[0] + ")"

tweet_text = "{}\n天気は{}予報で、最高気温予想は{}だそうです。\n今日も一日頑張りましょう。(from lambda)".format(today,today_w, temp)
client.create_tweet(text=tweet_text)
```

先に定義した辞書から取得した日時の曜日を取り出し、
"**mm/dd(weekday)**"のような形に変換する。

:::note info
**【Lambda内の実行時間】**
datetime.now()などを使う場合は注意が必要です。
Lambda実行時の日時は日本時間ではなくUTCであるため、
日本時間にするにはtimedeltaを使って(+9h)の調整が必要になります。
:::

あとはこれまでに取得した日付や天気情報を、
定型文にformat関数を用いて挿入してツイートする。

**<font color="blue">▼create_tweetのリファレンス▼</font>**

https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets

# 3. Lambdaへコードをデプロイ
Lambdaの編集画面で直接コードを書くことも可能ですが、
今回は事前に書いたpyファイルをアップロードしていきます。

またLambda内で使用できるモジュールには限りがあるため、
使用するモジュールによっては別で準備する必要があります。

今回はスクリプトのpyファイルと一緒に、
zipファイルに圧縮した状態でLambdaにアップロードします。

なおLambdaへアップロードする際は必ず **<font color="red">スクリプトファイルの名前は、
「lambda_function.py」にする</font>** 必要があります。

:::note info
**【パッケージをzipする際の注意点】**
パッケージとpyファイルをzipファイルに圧縮する際は、
画像のように個別に選択した状態で行います。
(2点を1つのファイルに入れてから圧縮するとエラーになる)
:::

![zipへ圧縮.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/14570ca6-0ea0-5f21-7a11-e173defe7a43.png)
pythonのコードを書いたpyファイルとパッケージを全てまとめて
zipファイルに圧縮し、アップロードします。
![lambdaにzipをアップロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/7f4a3ac6-e1ca-8666-c6c1-2da714a12d67.png)
今回はとても多くのパッケージを含んでいるため、
非常に大きなzipファイルとなってしまいました。

どうやらレイヤー機能を使うとパッケージを準備せず、
直接Lambda内でimportできるようなのですが、
今回は分かりやすい方法を採用しました。

そしてここで必要になるのが **<font color="red">ssm連携の権限設定</font>** です。
きちんと権限を設定しないとこの後のテスト段階で
延々とエラーバトルになるので必ず設定します。

**<font color="blue">▼Lambdaの権限設定▼</font>**

https://dev.classmethod.jp/articles/sugano-039-ssm-paramstore-lambda/


# 4. Lambdaのテスト
最後にデプロイしたコードをテストしていきます。
![lambda_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/7b2874e2-8c38-2328-a2b1-7c265aaf0655.png)

今回は初期設定のテストを行っただけですが、
エラーではなく **「Response」** が表示されていれば成功です。

実際に連携させたtwitterを見てみると、
設定したテキストがツイートされていることが確認できます。
(天気はわりと当たるが、気温は低く出がち？)
![tweet.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/fbaf7717-949f-3b0d-0615-0810620f6958.png)
# 5. cloud watchの設定
最後にLambdaのトリガーを設定します。
このLambdaは毎朝8:00きっかりに働いて貰おうと思います。

作成したLambdaの設定から「トリガーを選択」を選びます。
![トリガーを追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/67456ae3-0b40-cba1-62d7-2d309212280a.png)
EventBridgeを選択し、
![イベントブリッジを選択.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/71daac18-f113-8232-a5e7-34b2d357b36b.png)
時間をUTCの23:00(日本時間だと+9hで8:00)に設定します。
![時間を設定する.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2512226/3de9f562-1bca-6285-d36b-3d31c4e529b8.png)
これで毎朝8:00にLambdaが自動で実行されます。

実際に上に添付したツイートの画像を見てみると、
投稿時間が8:00ジャストであることが分かると思います。

**<font color="blue">▼EventBridgeの参考リンク▼</font>**

https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-create-rule-schedule.html

# 6. 所感と今後の展望
## 6-1. 躓いた点
**・サービスの全体像と繋がり
・IAMロールの仕様
・JSONファイルの扱い**

今回AWSのサービスに触れるのは初めてで、
<font color="red">自分のやりたいことに対してどのサービスを使うべきか</font>、
その全体像を把握するのがまず躓いた点でした。

サービスの全体像はAWSが以前に公開している
サミットのスライド資料を参考に少しずつ理解しました。

**<font color="blue">▼2019年サミット資料/動画▼</font>**

https://aws.amazon.com/jp/summits/tokyo-osaka-2019-report/

そして各サービスを連携させるにあたって
必ず必要になる<font color="red">IAMロールや権限</font>の設定の問題。

本記事で権限については触れていませんが、
基本一人での開発ではFullAccessで対処していました。

チームでの開発の場合は各ロールに権限の範囲を
適切に設定することで安全に開発ができると思います。

最後は<font color="red">JSONファイルの扱い</font>についてですが、
これはスクレイピング未経験だったためです。

次回はデータを取得してJSONファイルとして扱い、
S3バケットへの保存をするのでここで一気に学びました。

## 6-2. 今後の展望
今回は初回ということで基本的なLambdaの使い方について、
前回学んだTwiiterAPIと併せて練習してみました。

次回からはこのLambdaの機能を利用して、
 **<font color="red">自動でTwitter上からデータを取得する機能</font>** を作ります。

ちなみに今回のコードの応用だけで簡単な
Twitterbotも作れるのでそれだけでも楽しいと思います。

今回詳細は省きながら書かせて頂きましたがいずれは、
**・AWSの登録方法
・IAMロールの仕様
・SSMでのパラメータ作成方法
・cloudwatchの仕様
・lambdaのレイヤー**
あたりの詳細についても記事を書きたいと考えています。

## 6-3. さいごに
本記事を読んで頂いてありがとうございました。
コメントや質問、お気軽に宜しくお願い致します。

# 次回の記事

https://qiita.com/kawanago_py/items/aabbe6c6d129c915f2ad

# その他の参考リンク

https://qiita.com/r_saiki/items/8ae6df4c808b74bd824e

https://dev.classmethod.jp/articles/openweather_-pyowm/

https://www.fenet.jp/aws/column/aws-beginner/902/