AWSLambda + Python3 でLINEに通知してくれるToDoリストを作った話


作り始めた経緯

一か月前ほどからJavaを使ってandroidアプリ開発の勉強を始めたのですが、ToDoリストを作っている途中で

「よく考えたら普段使いはPCiPhoneだった:innocent:

ということに気づき、このまま使いもしないandroidアプリを作るくらいだったらとりあえずPCからタスク管理ができるものを作ろうということで作り始めました。


AWSを使う

AWS(Amazon Web Service)はクラウドで様々なことを行えるサービスです

登録後12か月は無料枠がありますし、個人で使う分には利用料金もそこまでしないので安心です。

AWS登録はこちら:point_right: https://aws.amazon.com/jp/

今回は様々なサービスがある中でLambdaS3CloudWatchEventsを使っていきます。

Lambda:関数を作って何かのトリガーで実行する機能

S3:ファイルを保存するストレージ

CloudWatchEvents:指定した時間に関数を叩いてくれる

くらいの認識で大丈夫だと思います。


システム概要

AWSflow1.png

分かりにくいですがこんな感じに作っていきます。


Lambda関数を作る

まずはLambdaの関数を作らなければ何も始まりませんので作っていきます。

まず関数の作成を押します。

AWS1.png

一から作成を選択します。

AWS2.png

すぐ下にこのような画面が出るので関数名と使う言語を入力し関数の作成を押します。

AWS3.png

なんかこんな画面が出てきたらOKです。

AWS4.png


ロールの設定

ロールにポリシーをアタッチしておかないと許可がなく実行できなかったりするのでロールの設定をしていきます。

先ほどのページを下がっていくと実行ロールの欄に既存のロールというというところがあります。そこのhogehogeロールを表示しますというところをクリックしてください。

AWS5.png

すると、このようなページが出てくると思うのでポリシーをアタッチしますをクリックしてください。

AWS6.png

その先のページで下記3つのポリシーをアタッチしてください。

AWS7.png

これでポリシーの設定は完了です。


PythonでAWSを扱う

PythonでAWSを扱うためにはboto3というライブラリを使用します。

初期設定などは少し難しいので偉大なる先人様の記事を参考にしました。

:point_down:ココミテ:eye:

boto3を使ってS3をごにょごにょする


S3にタスクをアップロードする

まずはクライアント側のプログラムを作ります。

細かいプログラムは人に見せられるような代物ではないので、ここでは僕が作っていて詰まった点とその解決法を書いていきます。


受け渡しデータ

CSVファイルに渡すデータはタスク名、日付、時間の三つのデータです。

年のデータを渡していないので一年以内のタスクしか扱えません。


ClientErrorが出る

これはtry-exceptで例外処理できるのですが、素の状態だとエラー名が見つからないので

from botocore.exceptions import ClientError

してから

try:

hogehoge
except(ClientError):
hugahuga

してください。


データの受取型

with open した後に

reader = csv.reader(f)

するのですが、readerにはreaderオブジェクトとして帰るのでそのままは使えません。

僕はfor文を使って二重ループで内容を回収しました。


通知時間に到達しているかチェックする関数

ここからはLambda側で作っていきます。

なおLambdaにライブラリとコードをまとめたZIP形式でアップロードするためコーディングは自分側で行います。


プログラム概要

task_check_flow.png


データを保存するディレクトリ

Lambdaにデータを受け渡すためにS3にいったんCSV形式でタスクのデータを送っているため、それを受け取らなければいけません。

その際ダウンロードをする場所を/tmp/hogehoge.csvとしなければなりません。

s3 = boto3.resource('s3')

bucket = s3.Bucket('hoge')
bucket.download_file('hogehoge.csv','/tmp/hogehoge.csv')

のようにします。


handleについて

Lambdaは呼び出されたときhandle設定した関数にeventとcontextを渡して実行します。

今回はS3からデータを持ってきて利用するのでこの受け取った内容は使用しませんが、受け取らないとエラーを吐くのでしっかりと

def handle(event, context):

と記述する必要があります。

また、handle名は自由につけることができ

AWS8.png

上記のようにハンドラの欄から プログラム名.ハンドル名 の形式で設定します。


Lambda上でのタイムゾーン

Lambda上では(多分)リージョンにかかわらずtimeモジュールで持ってくる時間がグリニッジ標準時(GMT)になっています。当然僕は日本でこのプログラムを利用するため日本時間(JST)になおす必要があります。

AWS9.png

そのために環境変数の欄にキーをTZ、値をAsia/Tokyoと設定します。

するとtimeモジュールで持ってくる値が日本時間に変更されます。


LambdaからLambdaを叩く

Lambda関数から別のLambda関数を叩くには

import boto3

import json

clientLambda = boto3.client("lambda")
clientLambda.invoke(
FunctionName="send_line", #送る先の関数名
InvocationType="Event", #EventかRequestResponseどちらを受け取るか選ぶ
Payload=json.dumps(event)
)

ここも少し難しかったので下記の先人様の記事を参考にさせていただきました。

AWS LambdaからLambda呼んでハマった話。

AWSのLambdaからLambdaを呼んで、Slackにメッセージを送信する


LambdaにZIP形式でアップロードする

ライブラリをインポートして使用している場合Lambdaで扱うためにはライブラリとプログラム本体をまとめたZIP形式のファイルでアップロードする必要があります。

C:\Users\user\programfile> pip install hogehoge -t ./

とかでプログラムと同階層に利用したライブラリを入れてください。

その後ZIP圧縮するのですが、この際ファイルの置いてあるディレクトリを圧縮すると階層が一つ深くなってhandleを掴めなくなってしまうようなので、ファイル自体を全選択して圧縮します。

AWS10.png

あとはここに投げて保存して終了です。

一応、ハンドルがつかめているか確認するためにテストイベントを実行します。

AWS11.png

テストイベントの設定を押して

AWS12.png

空のテストイベントを作成します。

その後テストから実行します。handleを掴めていてほかのエラーが出ていればとりあえずOKです。


CloudWatchEventsのcron式

cron式の記法は[分 時間 日 月 曜日 年]の形式で書きます。

cron式の日フィールドと曜日フィールドは同時に指定することができないとCloudWatchEventsのガイドに書いてあるので、どちらか一方でワイルドカード?(疑問符)を使用する必要があります。

ルールのスケジュール式 - Amazon CloudWatch Events

このプログラムは一時間に一回、00分に叩かれる必要があるので

[0 * * * ? *]

のように記述しました。


LINE Notifyの設定をする

設定はこちらから:point_right:LINE Notify

ラインに通知を行うためにLINEのAPIサービスのLINE Notifyの登録を行います。

ページを見ればわかるとは思いますが詰まった場合は

[超簡単]LINE notify を使ってみる

のページを参考にすることをお勧めします。


LINE送信部

アクセストークンが発行出来たら、先ほどのLambda関数でメッセージが生成されたときに叩かれるsend_line関数を作成していきます。


権限

ロールにアタッチする権限はS3のreadonly権限のみでOKです。


初期設定

url = "https://notify-api.line.me/api/notify"

token ="accesstoken" #各個取得したアクセストークン
headers = {"Authorization" : "Bearer "+ token}

これをスクリプト内に記述します。


プログラム概要

メッセージのCSVファイルを受け取って読み取って各メッセージごとにラインに送るだけ。


メッセージ複数送信

for mess in mess_lis:

message = mess
payload = {"message" : message}
r = requests.post(url ,headers = headers ,params=payload)

こうしておけば複数送信も可能です。


最後に

今回はAWSのさわりとしてこのようなToDoリストを作ってみました。

初めての投稿で読みずらい箇所多々あったとは思いますが、また何か作った際にはもっと改善して読みやすい記事を書けるようになってきますのでよろしくお願いします。:bow_tone1:

余談なのですが、最近「ほんとに使える「ユーザービリティ」(エリック・L.ライス著)」という本を読みました。振り返ってみるとこの記事は写真が多すぎた気がします。:pensive: