Discord上で動く辞書Bot作成話(後編)(Python[discord.py, beautifulsoup4, PyDrive] + GitHub + Heroku)
では後編に移ります。今回は備忘録的側面が強いため、分かりにくい所があるかもしれません。ご了承ください。
フレームデータを手に入れるために
フレームデータを手打ちするという作業は避けたかったため、DustLoopWikiのフレームデータをお借りすることにしました。ただWikiでのフレームデータは表の形で表示されているとはいえCSVの形式で転がっているはずもないわけです。ということでHTMLから該当する部分を抜き出してCSVにするプログラムが必要です。ここで登場するのがBeautifulSoup4というスクレイピングのライブラリになります。
import requests
from bs4 import BeautifulSoup
import csv
url = 'フレームデータのあるURL'
file = './data/[character name].csv'
r = requests.get(url)
#解析
soup = BeautifulSoup(r.text, 'html.parser')
tables = soup.find_all("table", {"class":"wikitable"})
table = list()
for t in tables:
table.append(t.find_all("tr"))
data = []
for rows in table:
for row in rows:
datarow = list()
for cell in row.find_all(["th", "td"]):
if cell.find("span", {"class" : "tooltiptext"}) != None:
cell.find("span", {"class" : "tooltiptext"}).decompose()
text = cell.get_text().replace("\n","").replace(", ",",")
datarow.append(text)
data.append(datarow)
with open(file,'w', newline="", encoding='utf-8-sig') as csvfile:
writer = csv.writer(csvfile)
writer.writerows(data)
find_allメソッドを用いて徐々に対象となる要素に近づいていきます。まずtableタグでwikitableクラスに囲まれている中身を取り出し、そこから列ごとに取り出すためにtrタグに囲まれている中身を取り出します。さらにセルごとに中身を取り出すために…と続けていくと、最終的にget_text()でセルの中身をテキストとして取り出すことができます。後はセル部分をリストとしてまとめて、列情報としてdataに格納します。最後にそれをcsv.writerで書き込めばOKです。スクリーンショットはラグナのフレームデータのページから出力した結果の一部になります。うまく抽出できていますね。
躓きやすい点として挙げられるのはfind_allメソッドの返り値でしょう。fild_all()は対象となる要素を抜き出し、リストの形式で返ってきます。雰囲気的にはfind_all("table").find_all("tr").find_all("td")…というように絞り込みたいところなのですが、find_allはそんなに都合よく解釈してくれません。リストの要素を指定するなり、forで回すなりして対処していきましょう(今回はwikitableクラス内のデータを取りたい、列ごとにリストでまとめたいためこのような処理にしていますが、単にtableタグの各セルの内容を取りたいのであればfind_all(["th", "td"])だけで十分でしょう)。
Googleドライブと連携しよう
格闘ゲーム用語の解説となるwordコマンドの充実化のため、Discordのメンバーにも手伝ってもらえるように共有ファイルのGoogleスプレッドシートから情報を取り込めるようにします。「編集協力お願いします!データ形式はcsvでお願いね!」はちょっと不親切ですし、googleスプレッドシートで編集するだけで更新が可能なら管理も楽ですよね。ということでGoogleドライブとの連携を行います。
この機能の実装に当たっては、以下の記事が非常に参考になりました。
最終的にはリモートサーバーであるHerokuからGoogleドライブに接続したいため、手法としては次のようになります。
0.(上の記事を参考にして)Googleドライブの認証の準備をする
1.auth.pyを作成し、実行してアクセストークン(DiscordのBotのトークンと同じですね、鍵となる認証コードです)を得る
2.アクセストークンを用いてメインのプログラムからGoogleドライブにアクセスする
3.対象のGoogleスプレッドシートをCSV形式で(Heroku上の一時ファイルとして)ダウンロードし、データベースを構築する
0.は記事を見ていただくことにして、1.から始めていきましょう。
メインと同ディレクトリに作成するauth.pyは次のようになります。(今回は真ん中のos.chdir…は要らないかも…)
import os
from pydrive.auth import GoogleAuth
os.chdir(os.path.dirname(os.path.abspath(__file__)))
gauth = GoogleAuth()
gauth.LocalWebserverAuth()
認証のためのsettings.yamlやjsonファイルを準備してこのファイルを実行すると、ブラウザが開いて認証確認画面が開きます。警告が出ますが気にせず「詳細」をクリックして先へ進みます。後は確認などの画面に従っていくと「認証が成功しました」との英文が表示されて認証が完了します。これでアクセストークンが発行され、プログラムからGoogleドライブにアクセスできるようになります。
次にGoogleドライブへのアクセスです。今回はPyDriveというラッパー(ライブラリを扱いやすくするライブラリで、自動車のハンドルカバーのようなものです)を用いているため、簡単にできます。
#Googleドライブ認証用
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
gauth = GoogleAuth()
gauth.CommandLineAuth()
#driveにアクセスするためのオブジェクト
drive = GoogleDrive(gauth)
これだけです。後はdriveを操作して目当てのデータを探します。
def update_worddata():
drive = GoogleDrive(gauth)
dir_id = drive.ListFile({'q': 'title = "BBTAG_Database"'}).GetList()[0]['id']
metadata = drive.ListFile({'q': '"{}" in parents and title = "word"'.format(dir_id)}).GetList()
data_id = metadata[0]['id']
f = drive.CreateFile({'id': data_id})
data_path = os.path.join('/tmp', 'tmp.csv')
f.GetContentFile(data_path, mimetype='text/csv')
with open(data_path, newline='', encoding='utf-8-sig') as csvfile:
readword = csv.reader(csvfile)
for row in readword:
wordlist[row[0]] = row[1]
os.remove(data_path)
drive.ListFileは引数で指定したファイルのタイプに合致したファイル・フォルダの情報をリスト形式で得る関数です。今回はBBTAG_Databaseフォルダ内の「word」という名前のファイルの情報を得ています。
f = drive.CreateFile({'id': data_id})
data_path = os.path.join('/tmp', 'tmp.csv')
f.GetContentFile(data_path, mimetype='text/csv')
この部分はファイルのダウンロード関連です。wordというファイルを/tmp/tmp.csvとしてcsvの形で保存します。(今回使用しているHerokuには/tmpという一時的にファイルを保存できる場所があります)
with open(data_path, newline='', encoding='utf-8-sig') as csvfile:
readword = csv.reader(csvfile)
for row in readword:
wordlist[row[0]] = row[1]
os.remove(data_path)
最後に保存したファイルを開いてデータを読み出して辞書の形で格納し、ファイルを消去して終了です。後は手動更新なりスケジューリングなどでupdate_worddata()を実行すれば更新が行われる、という仕組みです。
おわりに
こんな感じでBBTAG辞書Botは作成されました。データベースという性質上頻繁に使われることはありませんが、自分で作ったBotが使われているのを見るとなんだかうれしくなります。皆さんもDiscordBot、作ってみてはいかがでしょうか。意外と簡単ですよ!