俺のアウトプット

調べたこと、試したことを書きます

ブログに3日間書き込みしなかったら催促メールを送る

AWS Lambdaを使って、定期的にブログのトップページをチェック。 3日間書き込みがなかったら、催促メールを送り続ける。

あまり嬉しくないプログラムを作成します。

概要

大まかな流れは以下になります。

f:id:kitsugi:20181002221128p:plain

  1. CloudWatch Events が定期的にLambda関数を実行
  2. ブログのトップページから最新(最終)エントリ日を取得
  3. 3日間経過していたらメール送信

Lambda関数を作成する前に、下準備をします。

メール送信の準備

メールが送信できるように、Amazon SNSでトピックを作成してメール用のサブスクリプションを準備します。

トピックの作成

マネジメントコンソールから、サービス [アプリケーション統合] - [Simple Notification Service] を選択します。

トピックを選択して、[新しいトピックの作成] ボタンをクリック

f:id:kitsugi:20180805170748p:plain

トピック名を入力して[トピックの作成] ボタンをクリック

f:id:kitsugi:20181003052244p:plain

トピックが作成されます。

サブスクリプションの作成

トピック一覧から作成したトピックのARNリンクをクリックして、トピックの詳細を開きます。

f:id:kitsugi:20181003052611p:plain

[サブスクリプションの作成] ボタンをクリックします。

f:id:kitsugi:20181003052929p:plain

プロトコルとエンドポイントを入力します。

  • プロトコル: Email
  • エンドポイント: 送信したいメールアドレス

f:id:kitsugi:20181003053331p:plain

[サブスクリプションの作成]ボタンをクリックすると、確認用のメールが送信されます。
メール内のConfirm subscriptionリンクをクリックすると確認済となり、これで利用可能となります。

f:id:kitsugi:20181003053702p:plain

メールの確認

メールが受信できるか確認します。
トピックの詳細画面、左上の [トピックに発行] ボタンをクリック

f:id:kitsugi:20181003053932p:plain

件名とメッセージを入力して [メッセージの発行] ボタンをクリック

f:id:kitsugi:20181003054335p:plain

メールが届いたら成功です。
トピックARNは後で利用するので、メモ(コピー)しておいてください。

ロールの準備

Lambdaからメール送信ができるように、IAMロールを作成します。

マネジメントコンソールから、サービス [セキュリティ、 アイデンティティ、 コンプライアンス] - [IAM] を選択します。

サイドパネルの [ロール] から、[ロールの作成] ボタンをクリックします。

ロールを使用するサービスを選択

AWSサービスのLambdaを選択して、[次のステップ:アクセス権限] ボタンをクリックします。

f:id:kitsugi:20181003062548p:plain

ポリシーの割り当て

Lambdaからメール送信ができるように、以下のポリシーにチェックをつけます。

  • AWSLambdaBasicExecutionRole
  • AmazonSNSFullAccess

ポリシーのフィルタで絞り込むと探しやすいです。

f:id:kitsugi:20181003062557p:plain

ロールの確認と作成

ロール名 lambda-check-entry-date を入力し、ポリシーを確認。

問題がなければ、[ロールの作成] ボタンをクリックします。

f:id:kitsugi:20181003062605p:plain

以上で下準備は終わりです。

今回もAWS Cloud9上から作成していきます。

ローカル上にLambda関数を作成

ローカル上にLambda関数を作成します。

右側サイドバーにある、[AWS Resources] を選択して [AWSリソース] ウィンドウを開き、[λ+] ボタンをクリックします。

  • 関数名
    • checkEntryDate
  • アプリケーション名
    • CheckEntryDate
  • ランタイム
    • Python 3.6
  • 設計図
    • hello-world-python3
  • 関数トリガー
    • none
  • メモリ
    • 128MB
  • ロール
    • Choose an existing role
      • lambda-check-entry-date (下準備で作成したロール)

外部モジュールのインストール

Pythonの外部モジュールをインストールします。

  • Requests
    • 使い勝手のよいHTTPライブラリ
  • Beautiful Soup
    • スクレイピングでよく使われるHTMLパーサー
  • dateutils
    • 日付操作用ライブラリ
  • pytz
    • タイムゾーンライブラリ

ターミナルから、CheckBlogディレクトリ直下にインストールします。

$ cd CheckBlog/
$ pip-3.6 install requests -t .
$ pip-3.6 install beautifulsoup4 -t .
$ pip-3.6 install dateutils -t .
$ pip-3.6 install pytz -t .

-tはどこにインストールするかのターゲットを、.はカレントディレクトリを意味します。

必要なモジュールがインストールされました。

template.yaml

template.yaml に環境変数とスケジュールを登録します。

環境変数の使い方は、下記のエントリを参照してください。

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless Specification template describing your function.
Resources:
  checkBlog:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: checkBlog/lambda_function.lambda_handler
      Runtime: python3.6
      Description: ''
      MemorySize: 128
      Timeout: 15
      Role: 'arn:aws:iam::123456789012:role/lambda-check-entry-date'
      CodeUri: .debug/
      Environment:
        Variables:
          site: 'https://oreout.hatenablog.com'
          period: '3'
          topic: 'arn:aws:sns:ap-northeast-1:123456789012:check_blog'
          subject: '俺の通知'
          body: |
            何怠けとるねん!
            そろそろブログを更新して!
      Events:
        CheckBlogScheduledEvent:
          Type: Schedule
          Properties:
            Schedule: rate(6 hours)

環境変数

  • site
    • チェックするブログのトップページ
  • period
    • 最終投稿日からの何日間で判定するか
  • topic
    • メール送信の準備て作成したトピックARN
  • subject
    • メール件名
  • body
    • メール本文 (改行する場合はパイプ|を定義して次行に本文)

スケジュール

起動する間隔をRate式で定義します。

rate(数値 単位)

単位は を指定できます。

注意点として、数値が複数の場合は、単位が複数形になります。

Rate式は Rate または Cron を使用したスケジュール式 を参照してください。

Lambda関数

下記コードに差し替えて、保存 (Command+S) します。

サンプルのため、例外処理は考慮していません。

lambda_function.py

import boto3
import os
import requests
from bs4 import BeautifulSoup
from datetime import datetime
from dateutil import relativedelta
from dateutil import parser
from pytz import timezone

SITE = os.environ['site']
PERIOD = int(os.environ['period'])
TOPIC = os.environ['topic']
SUBJECT = os.environ['subject']
BODY = os.environ['body']

def lambda_handler(event, context):
    r = requests.get(SITE)
    soup = BeautifulSoup(r.text, 'html.parser')
    entry = soup.find('time').get('datetime')
    
    # 最後の投稿日
    latest = parser.parse(entry).date()

    # N日前
    target = datetime.now(timezone('Asia/Tokyo')) - relativedelta.relativedelta(days=PERIOD)
    target = target.date()

    if target > latest:
        sns = boto3.client('sns')
        sns.publish(TopicArn=TOPIC, Subject=SUBJECT, Message=BODY)
        return False
    else:
        return True
  • requestsでブログのトップページをGET
  • レスポンス結果をBeautifulSoupに渡してHTMLパース
  • 最初のtimeタグ(投稿日の降順)を取得して、datetime属性の値(日付)を取得
  • 現在日と比較してN日間経過している場合はメール送信

実行とデプロイ

ローカルで実行確認、問題がなければデプロイします。

ローカルの実行、デプロイの仕方は、下記のエントリを参照してください。

これで、ブログをさぼっていたら、催促メールが飛んでくるようになりました。

参考