概要
以下の記事でPythonスクリプトから各APIを操作する方法をを紹介しました。
今回は、
- QiitaAPIを使って記事一覧からランダムで取得
- TwitterAPIを使っていい感じに投稿する
- Incoming Webhookを使って実行結果をSlackに通知する
- AWS lambdaに乗せて実行をする
- AWS CloutWatchで定期実行する
という部分を実装してみようと思います。
手順
環境
- Python 3.6.1
- requests-oauthlib==0.8.0
- requests==2.18.4
前提
以下のものは事前に用意しておいてください。
-
Python関連
- requestsのインストール
- requests-oauthlibのインストール
-
Twitter関連
- Twitterアカウント
- Consumer Key
- Consumer Secret
- Access Token
- Access Token Secret
-
AWS関連
- AWSアカウント
-
Qiita関連
- Qiitaアカウント
- Qiita記事
-
Slack関連
- Incoming Webhookの設定
- 通知先Chanel
また、今回はリファクタリング等はしていないので冗長的なコーディングになってしまっている部分がありますが、本質ではないので今回はそのままとさせていただきますorz
Twitterへの投稿
まずはQiitaAPIを利用して、ランダムで投稿記事を取得し、Twitterに投稿するところまで実装してみましょう。
CONSUMER_KEY = "{コンシューマーキー}"
CONSUMER_SECRET = "{コンシューマーシークレット}"
ACCESS_TOKEN = "{アクセストークン}"
ACCESS_TOKEN_SECRET = "{アクセストークンシークレット}"
import json, config, requests, random, sys #必要なライブラリの読み込み
from requests_oauthlib import OAuth1Session #OAuthのライブラリの読み込み
CONSUMER_KEY = config.CONSUMER_KEY
CONSUMER_SECRET = config.CONSUMER_SECRET
ACCESS_TOKEN = config.ACCESS_TOKEN
ACCESS_TOKEN_SECRET = config.ACCESS_TOKEN_SECRET
twitter = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) #認証処理
TWITTER_API_HOST = "https://api.twitter.com/1.1" #TwitterAPIホスト
STATUSES_UPDATE_ENDPOINT = "/statuses/update.json" #ツイートエンドポイント
TARGET_USER_NAME = "{Qiitaユーザー名}" #取得対象のQiitaユーザー名
QIITA_API_HOST = "https://qiita.com/api/v2" #QiitaAPIホスト
USER_ENDPOINT = f"/users/{TARGET_USER_NAME}" #ユーザー情報取得エンドポイント
USER_ITEMS_ENDPOINT = f"/users/{TARGET_USER_NAME}/items" #ユーザー記事一覧取得エンドポイント
PAGE_PER_COUNT = 20 #1ページ辺りの記事取得件数
response = requests.get(
QIITA_API_HOST + USER_ENDPOINT
).json() #対象Qiitaアカウント情報をJsonで取得
totalItemCount = response['items_count'] #投稿総数を取得
pageCount = totalItemCount // PAGE_PER_COUNT #1ページ辺りの記事取得件数で取得出来たページ数を取得
hasSurplus = totalItemCount % PAGE_PER_COUNT != 0 #余りの記事数を取得
totalPageCount = pageCount + 1 if hasSurplus else pageCount #余りの記事数があった場合、端数分のページ数を追加
params = {
"page": random.randint(1,totalPageCount), #ページ番号をランダム生成
"per_page": PAGE_PER_COUNT
} #記事一覧をページ番号と1ページ辺りの表示件数を指定するためのパラメータを生成
response = requests.get(
QIITA_API_HOST + USER_ITEMS_ENDPOINT,
params=params
).json() #対象Qiitaアカウントの記事一覧を取得
if( response == [] ): #記事一覧が空だった場合
print("アイテムを取得出来ませんでしたので終了します。")
sys.exit() #処理終了
item = random.choice(response) #取得した一覧からランダムで記事を取得
url = item["url"] #記事URLを取得
title = item["title"] #記事タイトルを取得
tags = map(
lambda element: ("#" + element["name"]),
item["tags"]
) #タグ一覧をTwitterハッシュ化して配列として再生成
tagsStr = ' '.join(list(tags)) #タグ一覧を文字列結合
params = {
"status" : "【Qiita】" +title + "\n" + tagsStr + "\n" + url #ツイート内容を生成
} #Postするパラメータを生成
res = twitter.post(
TWITTER_API_HOST + STATUSES_UPDATE_ENDPOINT ,
params = params
) #APIにPost通信
if res.status_code == 200: #正常投稿出来た場合
print("Success.")
else: #正常投稿出来なかった場合
print("Failed. : %s"% vars(res))
確認
python script.py
それっぽくツイート出来てますね!
ただ、Qiitaのタグで「.」や「-」が含まれてると、Twitterのハッシュタグがうまく動きませんが、それは今回は妥協しましょうorz
Slackへの通知
次に処理結果をSlackに通知するところを実装してみましょう。
import json, config, requests, random, sys #必要なライブラリの読み込み
from requests_oauthlib import OAuth1Session #OAuthのライブラリの読み込み
CONSUMER_KEY = config.CONSUMER_KEY
CONSUMER_SECRET = config.CONSUMER_SECRET
ACCESS_TOKEN = config.ACCESS_TOKEN
ACCESS_TOKEN_SECRET = config.ACCESS_TOKEN_SECRET
twitter = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) #認証処理
INCOMING_WEBHOOK_URL = config.INCOMING_WEBHOOK_URL
TWITTER_API_HOST = "https://api.twitter.com/1.1" #TwitterAPIホスト
STATUSES_UPDATE_ENDPOINT = "/statuses/update.json" #ツイートエンドポイント
TWITTER_USER_NAME = "Bakira_Tech_Bot"
QIITA_USER_NAME = "bakira" #取得対象のQiitaアカウント名
QIITA_API_HOST = "https://qiita.com/api/v2" #QiitaAPIホスト
USER_ENDPOINT = f"/users/{QIITA_USER_NAME}" #ユーザー情報取得エンドポイント
USER_ITEMS_ENDPOINT = f"/users/{QIITA_USER_NAME}/items" #ユーザー記事一覧取得エンドポイント
PAGE_PER_COUNT = 20 #1ページ辺りの記事取得件数
response = requests.get(
QIITA_API_HOST + USER_ENDPOINT
).json() #対象Qiitaアカウント情報をJsonで取得
totalItemCount = response['items_count'] #投稿総数を取得
pageCount = totalItemCount // PAGE_PER_COUNT #1ページ辺りの記事取得件数で取得出来たページ数を取得
hasSurplus = totalItemCount % PAGE_PER_COUNT != 0 #余りの記事数を取得
totalPageCount = pageCount + 1 if hasSurplus else pageCount #余りの記事数があった場合、端数分のページ数を追加
params = {
"page": random.randint(1,totalPageCount), #ページ番号をランダム生成
"per_page": PAGE_PER_COUNT
} #記事一覧をページ番号と1ページ辺りの表示件数を指定するためのパラメータを生成
response = requests.get(
QIITA_API_HOST + USER_ITEMS_ENDPOINT,
params=params
).json() #対象Qiitaアカウントの記事一覧を取得
if( response == [] ): #記事一覧が空だった場合
requests.post(
INCOMING_WEBHOOK_URL,
data = json.dumps(
{
"attachments":[
{
"fallback":"Qiita Article is Empty.",
"pretext":"Bot Result.",
"color":"#FF0000",
"fields":[
{
"title":"Qiita Item is Empty",
"value":"There was no Article"
}
]
}
]
}
))
sys.exit() #処理終了
item = random.choice(response) #取得した一覧からランダムで記事を取得
url = item["url"] #記事URLを取得
title = item["title"] #記事タイトルを取得
tags = map(
lambda element: ("#" + element["name"]),
item["tags"]
) #タグ一覧をTwitterハッシュ化して配列として再生成
tagsStr = ' '.join(list(tags)) #タグ一覧を文字列結合
params = {
"status" : "【Qiita】" +title + "\n" + tagsStr + "\n" + url #ツイート内容を生成
} #Postするパラメータを生成
res = twitter.post(
TWITTER_API_HOST + STATUSES_UPDATE_ENDPOINT ,
params = params
) #APIにPost通信
if res.status_code == 200: #正常投稿出来た場合
resDic = json.loads(res.text)
id = resDic["id"]
requests.post(
INCOMING_WEBHOOK_URL,
data = json.dumps(
{
"attachments":[
{
"fallback":"Tweet Success.",
"pretext":"Bot Result.",
"color":"#00FF00",
"fields":[
{
"title":"Tweet Success.",
"value":f"Tweet URL : https://twitter.com/{TWITTER_USER_NAME}/status/{id}"
}
]
}
]
}
))
else: #正常投稿出来なかった場合
requests.post(
INCOMING_WEBHOOK_URL,
data = json.dumps(
{
"attachments":[
{
"fallback":"Tweet Failed.",
"pretext":"Bot Result.",
"color":"#FF0000",
"fields":[
{
"title":"Tweet Failed.",
"value":f"Error.[{res.status_code}]"
}
]
}
]
}
))
細かい部分は今後の課題として、一旦しっかりSlackへの通知が出来ていそうです。
AWS-lambdaへ登録
Scriptをlambda向けに修正
先ほど作ったスクリプトはコマンドから実行可能ですが、lambda上では動かないので少し手を加えます。
修正点としては、
- ファイル名を
lambda_function.py
に変更 - 全体を
lambda_handler(event, context)
関数にする -
設定値を環境変数化
- ※環境変数の指定の仕方は後述します。
修正後のファイルは以下となります。
import json, os, requests, random, sys #必要なライブラリの読み込み
from requests_oauthlib import OAuth1Session #OAuthのライブラリの読み込み
def lambda_handler(event, context):
CONSUMER_KEY = os.environ.get('CONSUMER_KEY')
CONSUMER_SECRET = os.environ.get('CONSUMER_SECRET')
ACCESS_TOKEN = os.environ.get('ACCESS_TOKEN')
ACCESS_TOKEN_SECRET = os.environ.get('ACCESS_TOKEN_SECRET')
twitter = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) #認証処理
INCOMING_WEBHOOK_URL = os.environ.get('INCOMING_WEBHOOK_URL')
TWITTER_API_HOST = "https://api.twitter.com/1.1" #TwitterAPIホスト
STATUSES_UPDATE_ENDPOINT = "/statuses/update.json" #ツイートエンドポイント
TWITTER_USER_NAME = os.environ.get('TWITTER_USER_NAME')
QIITA_USER_NAME = os.environ.get('QIITA_USER_NAME') #取得対象のQiitaアカウント名
QIITA_API_HOST = "https://qiita.com/api/v2" #QiitaAPIホスト
USER_ENDPOINT = f"/users/{QIITA_USER_NAME}" #ユーザー情報取得エンドポイント
USER_ITEMS_ENDPOINT = f"/users/{QIITA_USER_NAME}/items" #ユーザー記事一覧取得エンドポイント
PAGE_PER_COUNT = 20 #1ページ辺りの記事取得件数
response = requests.get(
QIITA_API_HOST + USER_ENDPOINT
).json() #対象Qiitaアカウント情報をJsonで取得
totalItemCount = response['items_count'] #投稿総数を取得
pageCount = totalItemCount // PAGE_PER_COUNT #1ページ辺りの記事取得件数で取得出来たページ数を取得
hasSurplus = totalItemCount % PAGE_PER_COUNT != 0 #余りの記事数を取得
totalPageCount = pageCount + 1 if hasSurplus else pageCount #余りの記事数があった場合、端数分のページ数を追加
params = {
"page": random.randint(1,totalPageCount), #ページ番号をランダム生成
"per_page": PAGE_PER_COUNT
} #記事一覧をページ番号と1ページ辺りの表示件数を指定するためのパラメータを生成
response = requests.get(
QIITA_API_HOST + USER_ITEMS_ENDPOINT,
params=params
).json() #対象Qiitaアカウントの記事一覧を取得
if( response == [] ): #記事一覧が空だった場合
requests.post(
INCOMING_WEBHOOK_URL,
data = json.dumps(
{
"attachments":[
{
"fallback":"Qiita Article is Empty.",
"pretext":"Bot Result.",
"color":"#FF0000",
"fields":[
{
"title":"Qiita Item is Empty",
"value":"There was no Article"
}
]
}
]
}
))
sys.exit() #処理終了
item = random.choice(response) #取得した一覧からランダムで記事を取得
url = item["url"] #記事URLを取得
title = item["title"] #記事タイトルを取得
tags = map(
lambda element: ("#" + element["name"]),
item["tags"]
) #タグ一覧をTwitterハッシュ化して配列として再生成
tagsStr = ' '.join(list(tags)) #タグ一覧を文字列結合
params = {
"status" : "【Qiita】" +title + "\n" + tagsStr + "\n" + url #ツイート内容を生成
} #Postするパラメータを生成
res = twitter.post(
TWITTER_API_HOST + STATUSES_UPDATE_ENDPOINT ,
params = params
) #APIにPost通信
if res.status_code == 200: #正常投稿出来た場合
resDic = json.loads(res.text)
id = resDic["id"]
requests.post(
INCOMING_WEBHOOK_URL,
data = json.dumps(
{
"attachments":[
{
"fallback":"Tweet Success.",
"pretext":"Bot Result.",
"color":"#00FF00",
"fields":[
{
"title":"Tweet Success.",
"value":f"Tweet URL : https://twitter.com/{TWITTER_USER_NAME}/status/{id}"
}
]
}
]
}
))
else: #正常投稿出来なかった場合
requests.post(
INCOMING_WEBHOOK_URL,
data = json.dumps(
{
"attachments":[
{
"fallback":"Tweet Failed.",
"pretext":"Bot Result.",
"color":"#FF0000",
"fields":[
{
"title":"Tweet Failed.",
"value":f"Error.[{res.status_code}]"
}
]
}
]
}
))
ライブラリを同階層にインストール
lambdaではpipコマンドでモジュールをインストールすることが出来ないので、サードパーティ製のモジュールが必要な場合はzip化してアップロードする必要があります。
今回は
- requests
- requests_oauthlib
をzipに含める必要があります。
作業ディレクトリで以下のコマンドを実行してください。
pip install requests -t .
pip install requests_oauthlib -t .
そうすると以下のように展開されます。
.
├── certifi
├── certifi-2017.11.5.dist-info
├── chardet
├── chardet-3.0.4.dist-info
├── idna
├── idna-2.6.dist-info
├── oauthlib
├── oauthlib-2.0.6.dist-info
├── requests
├── requests-2.18.4.dist-info
├── requests_oauthlib
├── requests_oauthlib-0.8.0.dist-info
├── lambda_function.py
├── urllib3
└── urllib3-1.22.dist-info
zip化
次にlambdaにアップロードするためにzip化しましょう。
作業ディレクトリで以下を実行してください。
zip -r upload.zip *
そうすると、upload.zipというファイルが出来ていると思います。
これをアップロードします。
AWS Lambdaの設定
Lambda関数の作成
次にAWSにログインし、コンピューティングメニューのLambdaを選択します。
関数の作成ボタンをクリックします。
一から作成を選択し、以下を設定の上、
- 名前 : 任意の名称
- ランタイム : Python 3.6
- ロール : テンプレートから新しいロールを作成
- ロール名 : 任意の名称
- ポリシーテンプレート : プルダウンからBasic Edge Lambda アクセス権限を選択
関数の作成ボタンをクリックしてください。
そうすると、Lambda関数が作成されるので詳細を設定していきます。
関数コード
コードエントリータイプを.ZIPファイルをアップロード、ランタイムをPython 3.6、ハンドラをlambda_function.lambda_handler、先ほど作成したzipファイルを選択します。
環境変数
Lambda関数単位で環境変数を指定することが出来ます。
今回は先ほどconfig.pyに記載していたものを環境変数として設定してみましょう。
テスト
設定が出来たらテスト実行してみましょう。
このような表示がされたら無事にスクリプトが動いたという事になります。
Slackにも通知が来ていますね。
Lambda関数を使うメリット
サーバーを用意する必要がないのでサーバーレス構成を実現することが可能です。
Lambdaは他にもAWSサービス群への操作をトリガーに処理を実行することが出来るので非常に便利です!!
また、ログファイルについてもデフォルトでCloudWatch Logsに吐き出してくれるのでとても助かりますね。
AWS CloudWatchの設定
スケジューリング登録
次はこのスクリプトを定期的に実行するように仕込みます。
今回は毎日00:00に投稿するようにスケジューリングしてみましょう。
実現するにはCloudWatch Eventsを利用します。
管理ツールメニューのCloudWatchを選択します。
イベントからルールの作成ボタンをクリックします。
イベントソースのスケジュールを選択し、Cron式に0 0 * * ? 0
を入力します。
ターゲットはLambda関数にし、機能は先ほど作ったLambda関数を選択します。
そして、設定の詳細ボタンをクリックします。
名前を任意の名称にしてルールの作成ボタンをクリックします。
これでルールが作成されたのでスケジューリングは完了です。
JSTとUTCの違い
ここですっかり次の日の00:00にLambda関数が実行されると思って油断していたら、、、
むむ。。。
9:00にLambda関数が実行されていました。
CloudWatchで指定するCronはUTC(GMT)で実行されるので、JSTに置き換えた時間指定をする必要があります。
厳密には9時間のズレがあるので-9時間をすれば良いのです。
今回の場合00:00に実行したいので、0 15 * * ? 0
と指定します。
これで正常に毎日00:00にLambda関数が実行されるようになりました♪
※処理のラグで多少通知時間がズレてますが良しとしましょう!!笑