PSO2の緊急クエストをSlackで通知

### きっかけ
研究室の先輩がPHANTASY STAR ONLINE2(以下PSO2)ジャンキーなので、先輩のために緊急クエストをLINEとかで通知してくれるようなbotを作ろうと始まったこの緩い開発業務。研究室の連絡手段がSlackであり、Python用のライブラリもあるようなので「とりあえずこれでいいか笑」という感じで作り始めた。

というのは口実で、実際は「院試のストレス解消のために、とにかくなんか作りたかった」という感じ笑。
### 開発環境
OS：Ubuntu 16.04.5
使用言語：Python 3.6.4
Webスクレイピング：BeautifulSoup4 4.6.0
Slack通知：slackweb 1.0.5
### 前準備
色々調べてるとどうやらSlack通知を受け取るために、incoming-webhookを有効にしないといけないと分かった。基本的には[この記事](https://qiita.com/shtnkgm/items/4f0e4dcbb9eb52fdf316)のように設定すれば簡単に有効化できる。
### とりあえずコード全部晒す
```python:pso2_via_slack.py
from bs4 import BeautifulSoup
import requests
import datetime
import re
import sys
import slackweb
import time

if sys.version_info[0] == 2:
    from urlparse import urljoin
elif sys.version_info[0] == 3:
    from urllib.parse import urljoin

def scrape_status(reqs, verbose=True):
    if verbose:
        if reqs.status_code < 200:
            sys.stdout.write('\nNow accsessing...\n')
        elif reqs.status_code < 300:
            sys.stdout.write('\nSuccsessed\n')
        elif reqs.status_code < 400:
            sys.stdout.write('\nNow Redirecting...\n')
        elif reqs.status_code < 500:
            raise Exception('Client error, Not scraped...')
        elif reqs.status_code < 600:
            raise Exception('Server error, Not scraped...')
        else:
            raise Exception('Unknown error...')

def maintenance_flag(property_list):
    if len(property_list) == 0:
        raise Exception('Server maintenance')

# ---------- init parameters ----------
WEEKDAYS = {
        0:'monday',
        1:'tuesday',
        2:'wednesday',
        3:'thursday',
        4:'friday',
        5:'saturday',
        6:'sunday'
        }
BASEURL = 'http://pso2.jp/players/'
ENCODE = 'utf-8'
YOUR_SLACK = 'incoming-webhookで取得した自身のSlack URL'
SLACK = slackweb.Slack(url=YOUR_SLACK)
SLACK_INFO = ''

while True:
    today = datetime.datetime.today()
    weekday = WEEKDAYS[today.weekday()+1]

    if '17:06' in str(today).split()[1]:
# ---------- scrape base URL site ----------
        htmldata = requests.get(BASEURL)
        scrape_status(reqs=htmldata)
        htmldata.encoding = ENCODE
        soup = BeautifulSoup(htmldata.content, 'lxml')
        href_list = list(set(soup.find_all('a', href='boost/')))
        maintenance_flag(href_list)

# ---------- get boost quest URL ----------
        boost_url = urljoin(BASEURL, href_list[0].get('href'))

# ---------- scrape boost quest timetable ----------
        boostdata = requests.get(boost_url)
        scrape_status(reqs=boostdata)
        boostdata.encoding = ENCODE
        boostsoup = BeautifulSoup(boostdata.content, 'lxml')
        today_quests = list(set(boostsoup.find_all('td', class_='day-{0}'.format(weekday))))
        maintenance_flag(today_quests)

# --------- informed via slack ----------
        quest_info = [str(content.div) for content in today_quests]
        for quest in quest_info:
            if quest != 'None':
                info = re.split('[<,>]', quest)
                SLACK_INFO += '{0}{1} {2} {3}\n'.format(info[18].replace('～', '-'),info[22],info[10],info[4])
        SLACK.notify(text=SLACK_INFO)

    time.sleep(60)
```
ほとんど殴り書きに近いスクリプト笑。他にも簡略化できる部分はかなりあるため、とりあえずのα版。流れとしては、翌日の曜日を取得後、requestsとBeautifulSoupでPSO2の公式サイトの緊急クエストテーブルまでのパスを取得、これをもう一度パースして、クエストの時間・名称を文字列に変換してSlackにぶん投げる、というもの。またスクリプトはサーバー内で永久稼働していないと使えないので、無限ループで何もせず待機、クエストが更新される午後5時になったらスクレイピングを開始して、クエストをSlackから通知する(本当は水曜日の午後5時に1週間分のクエストが更新される、今後直します)。基本的にバックエンドジョブで流す。
### 関数の説明
## scrape_status
requestsライブラリで取得したステータスコードの値に応じて、メッセージを表示したり、例外を返したりする部分。正直あってもなくてもどっちでもいい。単純に自分の勉強のために作ってみた。本当はtry-exceptでスマートに決めたかった。
### maintenance_flag
緊急クエストテーブルがメンテナンス中のときにスクレイピングすると、スクリプトのtoday_questが空のリストととなる。これを判断して例外を返すために作成。そのエラーメッセージもSlackで通知できればなおよし。
### 結果
![20190307_172020.jpg](https://qiita-image-store.s3.amazonaws.com/0/299965/013f7231-5999-6ee3-0ca5-837ef2e123d2.jpeg)

![20190307_172053.jpg](https://qiita-image-store.s3.amazonaws.com/0/299965/0786bf3f-5797-8023-fc45-e354db03b257.jpeg)

公式のタイムテーブルとスマホのSlack画面。時間を時系列順にソートしていないけど通知はできている。α版としてはいい出来かも笑。

### 注意点として
## スリープ時間に関して
ループ時にsleepを60秒にしているが、これを1桁にした暁には、Slack画面が通知で荒れることになる。LINEのスタンプ爆撃並みにイライラする。あとSlackの無料版はメッセージの一括削除ができないのでいちいち消していかないといけない。これが恐ろしくめんどくさい。今年一番の鬼畜プレイだった。
## datetimeの時間に関して
このスクリプトでは、datetimeの時間が午後5時6分にスクレピングするよう設定しているが、使用するLinuxサーバーによって通知が来る時間が異なる。実際の時間とUnix時間のギャップは予め確認しておき、環境に応じて適宜変える必要がある。本当はUnix時間と実時間を一致させたいが、方法を現在模索中。
### 最後に
スクレイピングは楽しかった。そして無料版のSlackはクソ。