きっかけ
研究室の先輩が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を有効にしないといけないと分かった。基本的にはこの記事のように設定すれば簡単に有効化できる。
とりあえずコード全部晒す
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で通知できればなおよし。
結果
公式のタイムテーブルとスマホのSlack画面。時間を時系列順にソートしていないけど通知はできている。α版としてはいい出来かも笑。
注意点として
スリープ時間に関して
ループ時にsleepを60秒にしているが、これを1桁にした暁には、Slack画面が通知で荒れることになる。LINEのスタンプ爆撃並みにイライラする。あとSlackの無料版はメッセージの一括削除ができないのでいちいち消していかないといけない。これが恐ろしくめんどくさい。今年一番の鬼畜プレイだった。
datetimeの時間に関して
このスクリプトでは、datetimeの時間が午後5時6分にスクレピングするよう設定しているが、使用するLinuxサーバーによって通知が来る時間が異なる。実際の時間とUnix時間のギャップは予め確認しておき、環境に応じて適宜変える必要がある。本当はUnix時間と実時間を一致させたいが、方法を現在模索中。
最後に
スクレイピングは楽しかった。そして無料版のSlackはクソ。