PythonとHerokuでKindle日替わりセールの情報を送ってくれるslackbotを作ってみる

まずはslackbotライブラリとスクレイピングするために必要なライブラリ(requestsBeautiful Soup)をインストールする。

$ pip install slackbot
$ pip install requests beautifulsoup4

ディレクトリ構造

$ tree
.
├── Procfile
├── libs
│   ├── __init__.py
│   └── my_functions.py
├── plugins
│   ├── __init__.py
│   └── my_mention.py
├── requirements.txt
├── run.py
└── slackbot_settings.py

実装

  • run.pyslackbot_settings.py
# -*- coding: utf-8 -*-

from slackbot.bot import Bot

def main():
    bot = Bot()
    bot.run()

if __name__ == "__main__":
    print('start slackbot')
    main()
# -*- coding: utf-8 -*-

import os

# botアカウントのトークンを指定
API_TOKEN = os.environ.get('SLACKBOT_API_TOKEN')

# このbot宛のメッセージで、どの応答にも当てはまらない場合の応答文字列
DEFAULT_REPLY = "何言ってんだこいつ"

# プラグインスクリプトを置いてあるサブディレクトリ名のリスト
PLUGINS = ['plugins']
  • my_functions.pyは以下の通りです。

Kindle日替わりセールの情報をスクレイピングする。

# -*- coding: utf-8 -*-

import re
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from bs4 import BeautifulSoup

headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'}

def kindle_daily_deal(url):
    text = ''

    s = requests.Session()
    retries = Retry(total=5,
                    backoff_factor=0.1,
                    status_forcelist=[500, 502, 503, 504])

    s.mount("https://", HTTPAdapter(max_retries=retries))

    html = s.get(url, headers=headers).text.encode('utf-8')
    soup = BeautifulSoup(html, features="html.parser")

    # CSSセレクターを使って日替わりセールリストを取得する
    carousel_list = soup.find('ol', attrs={'class': 'a-carousel'})

    # 本のタイトルと著者名を取得する
    title_author_list = carousel_list.select("[class='a-truncate-full']")

    # 本の価格を取得する
    price_list = carousel_list.select("[class='a-price-whole']")

    res = {}
    res['title'] = ['『{}』'.format(t.get_text()) for i, t in enumerate(title_author_list) if i % 2 == 0]
    res['author'] = [re.sub(r"[\n ]", "", a.get_text()) for i, a in enumerate(title_author_list) if i % 2 == 1]
    res['price'] = ['{}\n'.format(p.get_text()) for p in price_list]

    for i in range(len(price_list)):
        text += '📚 '+ res['title'][i] + '\n著者:' + res['author'][i] + '\n価格:' + res['price'][i]

    return text
  • my_mention.py

kindleというキーワードに反応して、今日のKindle日替わりセール情報を返事する。

# -*- coding: utf-8 -*-

import re
from slackbot.bot import respond_to     # @botname: で反応するデコーダ
from slackbot.bot import listen_to      # チャネル内発言で反応するデコーダ
from libs import my_functions           # 外部関数の読み込み

@respond_to(r'kindle', re.I)
@listen_to(r'kindle', re.I)
def kindle(message):
    url = 'https://www.amazon.co.jp/Kindle%E6%97%A5%E6%9B%BF%E3%82%8F%E3%82%8A/b/?ie=UTF8&node=3338926051&ref_=sv_nav_ebook_4'
    text = my_functions.kindle_daily_deal(url)
    msg = 'ほら!今日のKindle日替わりセールだぞ\n```' + text + '```'
    message.reply(msg)

デプロイ

requirements.txtとHerokuの設定ファイルProcfileを用意する。

$ pip freeze > requirements.txt

Procfile

worker: python run.py

slackのAPIトークンを環境変数として設定する。

$ heroku config:set SLACKBOT_API_TOKEN=xoxb-xxxxx

環境変数の確認。

$ heroku config

Settingsタブでpython buildpackを追加する。

f:id:michiru_7:20210505214020p:plain

あとは、Deployタブで書いた通りにコマンドを実行する。

$ heroku login
$ git init
$ heroku git:remote -a xxx
$ git add .
$ git commit -am "make it better"
$ git push heroku master

さて、プロセスを起動してみる。

$ heroku ps:scale worker=1

プロセスを確認する。

$ heroku ps

プロセスを停止するには、以下のコマンドを実行すれば良い。

$ heroku ps:scale worker=0

ログを見る。

$ heroku logs --tail

定期投稿

日替わりセールなので、やっぱり毎日指定した時間にメッセージを投稿したいです。 Herokuでの定期実行はHeroku Schedulerというアドオンが提供しているが、今回はpython-crontabというライブラリを用いて定期実行を行う。

python-crontabライブラリをインストールする(croniterのインストールも必要になる)。

$ pip install python-crontab
$ pip install croniter
  • cron.py

HerokuのタイムゾーンUTCなので、9時間の時差がある。

# -*- coding: utf-8 -*-

from crontab import CronTab

def main():
    cron = CronTab()
    job1 = cron.new('python3 kindle.py')
    # 毎日の20:00(JST)に実行する
    job1.setall('00 11 * * *')
    for res in cron.run_scheduler():
        print('The schedule has been executed.')

if __name__ == "__main__":
    main()
  • kindle.py
# -*- coding: utf-8 -*-

import re
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from slacker import Slacker
from bs4 import BeautifulSoup
from slackbot_settings import API_TOKEN

headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'}

def kindle_daily_deal(url):
    # 省略

def main():
    url = 'https://www.amazon.co.jp/Kindle%E6%97%A5%E6%9B%BF%E3%82%8F%E3%82%8A/b/?ie=UTF8&node=3338926051&ref_=sv_nav_ebook_4'
    text = kindle_daily_deal(url)
    msg = 'ほら!今日のKindle日替わりセールだぞ\n```' + text + '```'
    slack = Slacker(API_TOKEN)
    slack.chat.post_message('#reminder', msg, as_user=True)

if __name__ == "__main__":
    main()
  • Procfile
worker: python run.py
cron: python cron.py

プロセスを起動する。

$ heroku ps:scale cron=1

結果はこんな感じ

f:id:michiru_7:20210505214256p:plain