Lambdaでお手軽サーバーレス入門（外部ライブラリ使用編）

前回：[Lambdaでお手軽サーバーレス入門（CloudWatch Events編）](https://qiita.com/kakken1988/items/12c1af589ade9f780222)

前回は、天気予報を定期的に取得しましたが、ログに出力しただけでした。
今回は、Pythonのライブラリを使って、Twitterに投稿してみたいと思います。

# 前回のソース

[GitHubにアップしました](https://github.com/kenichi-ogawa-1988/qiita-lambda-cloudwatch-events/tree/12c1af589ade9f780222)

```python:lambda_function.py
import os
import json
from urllib.parse import urlencode
from urllib.request import urlopen
from datetime import datetime

API_ID = os.getenv('OWM_API_ID')
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'


def lambda_handler(event: dict, context):
    if not API_ID:
        print('ERROR: No API ID')
        return

    # get whether information from OpenWeatherMap
    url = 'https://api.openweathermap.org/data/2.5/weather?{}'.format(urlencode({
        'q': 'Ho Chi Minh City,VN',
        'units': 'metric',
        'appid': API_ID
    }))
    with urlopen(url) as f:
        raw_data = json.loads(f.read().decode('utf-8'))

    print('Location: {}, {}'.format(raw_data['name'], raw_data['sys']['country']))
    print('Time: {}'.format(datetime.fromtimestamp(raw_data['dt']).strftime(DATETIME_FORMAT)))
    print('Weather: {}, {}'.format(raw_data['weather'][0]['main'], raw_data['weather'][0]['description']))
    print('Temperature: {}℃'.format(raw_data['main']['temp']))
    print('Pressure: {:,}hPa'.format(raw_data['main']['pressure']))
    print('Humidity: {}%'.format(raw_data['main']['humidity']))


if __name__ == '__main__':
    lambda_handler({}, None)
```

# PythonでのTwitterライブラリ

Twitterの開発者サイト https://developer.twitter.com/en/docs/developer-utilities/twitter-libraries.html でたくさん紹介されています。
この中で、[Tweepy](https://github.com/tweepy/tweepy)を使ってみたいと思います。
（★がたくさんあるため、という理由）

## プロジェクトにインストール

通常Pythonのライブラリは `pip install tweepy` のようにインストールすると使えますが、今回は、プロジェクトのフォルダにインストールしたいので、少しコマンドラインを変えてみます。

```bash
cd /path/to/qiita-lambda-cloudwatch-events
pip install tweepy -t .
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143240/68f200dd-075d-4e38-70cf-035fac4caf48.png)

めっちゃエラー出ました…。

調べると、[StackOverflow](https://stackoverflow.com/a/44728772)で答えを見つけました。

```ini:setup.cfg
[install]
prefix=
```

このファイルをプロジェクト（lambda_function.pyと同じ場所）に置いて、再度試してみます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143240/d9fe77c0-ac66-5558-7898-5f1d8a0c882f.png)

無事、成功しました。

# Tweepyを使う

ソースを以下のように変更します。

```python:lambda_function.py
import os
import json
from urllib.parse import urlencode
from urllib.request import urlopen
from datetime import datetime

import tweepy

OWM_API_ID = os.getenv('OWM_API_ID')
TWITTER_CONSUMER_KEY = os.getenv('TWITTER_CONSUMER_KEY')
TWITTER_CONSUMER_SECRET = os.getenv('TWITTER_CONSUMER_SECRET')
TWITTER_ACCESS_TOKEN = os.getenv('TWITTER_ACCESS_TOKEN')
TWITTER_ACCESS_TOKEN_SECRET = os.getenv('TWITTER_ACCESS_TOKEN_SECRET')

DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'


def lambda_handler(event: dict, context):
    if not OWM_API_ID:
        print('ERROR: No OpenWeatherMap API ID')
        return

    # get whether information from OpenWeatherMap
    url = 'https://api.openweathermap.org/data/2.5/weather?{}'.format(urlencode({
        'q': 'Ho Chi Minh City,VN',
        'units': 'metric',
        'appid': OWM_API_ID
    }))
    with urlopen(url) as f:
        raw_data = json.loads(f.read().decode('utf-8'))

    print('Location: {}, {}'.format(raw_data['name'], raw_data['sys']['country']))
    print('Time: {}'.format(datetime.fromtimestamp(raw_data['dt']).strftime(DATETIME_FORMAT)))
    print('Weather: {}, {}'.format(raw_data['weather'][0]['main'], raw_data['weather'][0]['description']))
    print('Temperature: {}℃'.format(raw_data['main']['temp']))
    print('Pressure: {:,}hPa'.format(raw_data['main']['pressure']))
    print('Humidity: {}%'.format(raw_data['main']['humidity']))

    if TWITTER_CONSUMER_KEY and TWITTER_CONSUMER_SECRET and TWITTER_ACCESS_TOKEN and TWITTER_ACCESS_TOKEN_SECRET:
        # Post to Twitter
        twitter_auth = tweepy.OAuthHandler(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)
        twitter_auth.set_access_token(TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET)
        twitter_api = tweepy.API(twitter_auth)
        print('Your Twitter name is {}'.format(twitter_api.me().name))


        twitter_api.update_status(status='''{city} ({country}) の天気 ({time}時点)

{weather} - {description}
気温🌡 {temperature}℃
気圧👂 {pressure:,}hPa
湿度💦 {humidity}%
        '''.format(
            city=raw_data['name'],
            country=raw_data['sys']['country'],
            time=datetime.fromtimestamp(raw_data['dt']).strftime(DATETIME_FORMAT),
            weather=raw_data['weather'][0]['main'],
            description=raw_data['weather'][0]['description'],
            temperature=raw_data['main']['temp'],
            pressure=raw_data['main']['pressure'],
            humidity=raw_data['main']['humidity']
        ))


if __name__ == '__main__':
    lambda_handler({}, None)
```

# TwitterのAPIを使えるようにする

2018年の7月より、従来のように簡単にAPIキーを作れなくなったようです。
（恥ずかしながら今知りました）

なので、 https://developer.twitter.com/en/apply/user より申請しなければいけないようです。
申請が通ったら、[こちら](https://developer.twitter.com/en/apps/create)から作成できます。

私はこのように作ってみました。
![twitter01.png](https://qiita-image-store.s3.amazonaws.com/0/143240/14a260b2-b35e-1444-9418-cbe722cf12f1.png)

作成されると、概要が見れる画面に遷移するので、そのまま「Keys and tokens」タブをクリックします。
![twitter03.png](https://qiita-image-store.s3.amazonaws.com/0/143240/8d9c000f-e665-241b-0f35-9a258fa7ffd9.png)

下の「Access token & access token secret」にある「Create」をクリック。
![twitter04.png](https://qiita-image-store.s3.amazonaws.com/0/143240/52804bff-3c6c-fb51-1a6e-87b36bc1f429.png)

ここで登場した4つの値、

* API key
* API secret key
* Access token
* Access token secret

は、メモしておいてください。

## 環境変数を設定する

ここでLambda関数画面を開き、先程メモした4つの環境変数をそれぞれ設定します。
![twitter05.png](https://qiita-image-store.s3.amazonaws.com/0/143240/213c9308-fc92-0f8f-efae-32746e2d5a4c.png)

* TWITTER_CONSUMER_KEY <- API key
* TWITTER_CONSUMER_SECRET <- API secret key
* TWITTER_ACCESS_TOKEN <- Access token
* TWITTER_ACCESS_TOKEN_SECRET <- Access token secret

# Zipファイルを作る

さて、肝心のソースなんですが…
上記の画面で上半分に見切れているインラインエディタだと、ファイルのアップロードができません。
なので、Zipファイルを作り、アップロードします。

プロジェクトのフォルダはこの様になっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/143240/f85139a7-f08b-84f3-d709-cf700a98671b.png)

ここにあるファイルを **すべて** 一つのZipファイルにします。
必ず、ソースファイルがZipファイルの直下になるように作ってください。（ひとつ上のフォルダで作ろうとすると、そのフォルダがルートになる圧縮ソフトもあるため注意）
Macの場合は、`⌘+A`で全て選択して、「Compress 27 items」です。

出来上がったZipファイルを、Lambdaコンソール（先程環境変数を設定した画面）でアップロードします。
Function code内のCode entry typeを「Upload a .zip file」にし、ファイルとして先程圧縮したZipを選択します。
![twitter06.png](https://qiita-image-store.s3.amazonaws.com/0/143240/7357ac31-82d5-26e7-c587-5e22f58cd850.png)

Saveをクリックすると、Zipがアップロードされ、再びインラインエディタが使えるようになります。
![twitter07.png](https://qiita-image-store.s3.amazonaws.com/0/143240/fefab954-1572-cf18-d0ff-90dbe89101d9.png)
圧縮したファイルがすべて出ていますね！
これで成功です。

※トータルサイズが3MBを超える場合、インラインエディタは表示されません。

# テスト

まずは動くか直接実行してみます。
右上の「Test」をクリックすると…
![image.png](https://qiita-image-store.s3.amazonaws.com/0/143240/d62dc8f5-d1d6-b42e-4b86-414eb2fee6a8.png)
赤枠の部分、増えてます！

そして肝心の[自分のTwitter](https://twitter.com/ken_ch_s/status/1048117044682448896)を見ると…
![twitter08.png](https://qiita-image-store.s3.amazonaws.com/0/143240/65b7a36b-bc1b-2a94-fece-5b03f5dd2d0c.png)
増えてますね！

やばい…湿度が高くなってる＝スコールの予感？
（現在ベトナム南部は雨季です。あと1ヶ月ほどで終わると思いますが）

## 何かおかしい？

はい、 **時刻がUTC** です。
（その前の同じようなツイートは、ローカルマシンから実行したものです）

LambdaはすべてUTCで動くので、`datetime.strftime` の結果も、タイムゾーンを指定しなければUTCになります。
なので、あとでこっそり変えておきました。