LoginSignup
13
14

天鳳個室の自動成績管理アプリをLINE botとPythonで作る その①

Last updated at Posted at 2020-06-01

背景

  • COVID-19の感染拡大により雀荘が封鎖され行き場を失った私達は天鳳の個室を使ってセット麻雀をするようになった.
  • はじめは成績管理のためにログを手動でコピペしてGoogle Docsに保存していたが,毎日麻雀を打っているとだんだん面倒になってきた.
  • そこでログの自動ダウンロード・集計・グラフ表示等を自動で行うアプリをPythonで作成することにした.
  • さらに参加者全員(非エンジニアを含む)がいつでも情報を見ることができるように,インターフェースとしてLINE上から操作できるアプリケーションとして作成した.

概要

アプリ概要チャート

  • アプリは主に以下の2つの機能からなります.
    1. 天鳳のログを自動でダウンロードし保存する(図の紫矢印)
    2. ユーザ(LINE)のリクエストに応じて集計情報を返信する(図の緑矢印)
  • 本記事では項目1に関する部分について説明します.項目2については別の記事にします.
    test.png

この記事の続き

完成イメージ

  • ウェイクワード(ここでは「しゅうけい」や「ぐらふ」)に反応して,リクエストのメニュー(ここでは「収支」「着順」「チップ」「today])が表示されます.
  • 「収支」を選択すると,過去のログを全部足し合わせた合計の点数や対局数,チップ総数が表示されます.
  • 「ぐらふ」のウェイクワードからは過去の点数やチップの推移をグラフ化したものが表示できます.
  • 今回は実装していませんが,直接対局の収支や平均点数なども面白いかもしれませんね.

ui.png

使用言語・ツール・サービス・モジュール等

使用言語

  • Python 3.7.3

使用ツール・サービス

今回は無料で利用できる以下のサービスを使用してアプリを作成した.

Heroku

<2023.5.9 追記>
Herokuの無料枠が終了したため,無料では使用できなくなりました.
有料枠を利用するか,Firebaseなどの代替サービスの利用が必要そうです:muscle_tone2:

  • Herokuホーム
  • web上でプログラムを実行するためのサービス
  • 今回はLINE botの実行アプリとログスクレイピングスクリプトの2つの実行環境として利用

LINE bot (LINE Messaging API)

Dropbox API

  • ログの保管にはDropBoxを利用させていただいた.
  • APIを使えばDropbox上のファイルやフォルダをPythonで操作できる
  • 当初はもともと使っていたGoogle Drive(とGoogle Docs)のAPIを使ってアプリを作成していたが,Google Documentのファイル(.gdocs)がAPIに対応していない(僕が見つけられなかっただけ?)のでやめた.またテキストファイル(.txt)をGoogle Docsで編集すると.gdocsに変わる謎仕様にかなりイライラしたのでもう二度と使わないと思う.
  • その点DropboxのAPIはとても簡単でわかりやすく初心者の私にもすぐ使えた.オススメ!!!

AWS S3ストレージ

  • Amazonのクラウドサービス
  • LINEからのリクエストに応じてPythonで書いたグラフを一時的に保存し,そのファイルのリンクからLINEに画像を表示する.
  • わざわざS3を使わなくてもDropboxで行けるかもしれない(Dropboxを使い始める前にS3を使っていたので複数のサービスが混同してしまった).わかりにくくてすいません(汗

使用モジュール

  • Flask==0.12.2
  • line-bot-sdk==1.8.0
  • boto3==1.9.4
  • pandas==0.24.2
  • matplotlib==3.0.3
  • numpy==1.16.2
  • dropbox==10.1.2

天鳳個室のログ取得

今回は天鳳個室のログをスクレイピングで取得するところまでです.
続きもぼちぼち投稿していきます.

あらすじ

天鳳のログのダウンロード方法は 天鳳公式/ログに書かれている方法で行いました.
具体的には,

"https://tenhou.net/sc/raw/dat/sca{yyyymmdd}.log.gz"

({yyyymmdd}は取得するログの日付)のURLからurllibを用いて.gzファイル(scrape.gz)をダウンロードし,その中身を展開して一度.txtファイル(scrape.txt)にしておきます.__個室のログはsca__です.

次に,将来的にHerokuのサーバーからログにアクセスできるようにしたいため,ログをDropboxで管理できるようにします.

  • Dropbox上に空のログファイルを作成しておきます.
  • 上記のログファイルにその日のログを追記したあと,再びDropboxにアップロードします.
  • 毎日手動でスクリプトを実行するのもダサいので,毎日AM0:30に前日の分のログを自動で取得することにします.スクリプトの定期実行はHerokuで設定することができます.定期実行の方法はHerokuでお天気Pythonの定期実行 -Qiitaを参考にしました.
    スクリーンショット 2020-06-02 0.34.29.png

コード例

プリアンブル部

モジュール類はAnaconda環境であれば全部デフォルトで入っているはずです.  

  • ないものがあればpipやcondaでインストールしてください.

download4は自作モジュールで,Dropboxとのファイルのやり取りに使います(詳細は後で説明します).  

# scrape_log.py
# coding *-utf-8-*

import os
import pprint
import time
import urllib.error
import urllib.request
import gzip
import shutil
import datetime
from datetime import date,timedelta
import download4

関数の定義

可読性を上げるために,スクレイプ実行部分を関数化しておきます.

下のサイトにあるコードを使わせていただきました:

def download_file(url, dst_path):
    try:
        with urllib.request.urlopen(url) as web_file:
            data = web_file.read()
            with open(dst_path, mode='wb') as local_file:
                local_file.write(data)
    except urllib.error.URLError as e:
        print(e)

メイン部分

スクリプトを実行する前日のログを取得します.

  • Heroku(実行環境)のサーバーの時間がUTC(世界標準時間)になっているので9時間の時差の影響を考える必要があります!!.日本時間の0:30はUTCでは__前日__の15:30なので,スクリプトが実行される日付のログを取ってくればいいわけですね.(ちなみにローカルのテスト環境だと実行日の1日前のログをとってくるので,そのばあいはコメントアウトしているようにtimedeltaを使用して1時間(これで日付は1日前になる)ずらしています.)

  • 天鳳ログのスクレイプは.gzファイルで行うようにルールが定められているので,.gzファイルとしてスクレイプした後.txtファイルに書き直しています.

  • 取得したログを,Dropbox上のログファイルに追記します.
    自作モジュールdownload4上で定義したdownloadメソッドを用いて,過去のログを貯めていた.txtファイル(ここではlogvol1.txt)をDropboxからダウンロードし,スクレイプした前日分のログを追記した後,uploadメソッドで再アップロードします.

    • この時 ifを用いて自分たちの個室(例:C1234)の分のログだけ書き出すようにしています.
if __name__ == "__main__":
    dt_now = datetime.datetime.now()
    yyyymmdd = dt_now.strftime('%Y%m%d')
    # dt_1day_past = dt_now - timedelta(days=1) # 1時間前のログを探す
    # yyyymmdd = dt_1day_past.strftime('%Y%m%d')
    print(yyyymmdd)

    fname = 'sca{}.log.gz'.format(yyyymmdd)
    URL = "https://tenhou.net/sc/raw/dat/"+fname
    
    dst_path = 'scrape.gz'
    download_file(URL, dst_path)

    with gzip.open('scrape.gz', mode='r') as f_in:
        with open('scrape.txt', 'wb') as f_out:
            shutil.copyfileobj(f_in, f_out)
    with open('scrape.txt') as f:
        lines = f.readlines()


    download4.download("/logvol1.txt","temp.txt")
    with open("temp.txt",'a') as f:
        f.write("{}\n".format(yyyymmdd))
        for line in lines:
            roomid = line.split()[0]
            if roomid == "C1234": # 個室ID
                f.write("{}".format(line)) 
                print(line)

    download4.upload("temp.txt","/logvol1.txt")       

自作モジュールdownload4の中身についてです,

基本的にDropboxとのダウンロードとアップロードを関数化しているだけです.

  • DropboxのAPI利用登録,アプリケーション用フォルダの作成等は下のサイトを参考にしました.
  • アプリケーションを作成するとDropboxのホームディレクトリに「アプリ」という名前のフォルダができ,その直下が作業フォルダになるようです.
  • Dropbox APIのダウンロード(download_file(self,file_from,file_to))ではfile_toが無いとダウンロードできないようなので,予め空ファイルでいいので用意しておく必要がありました.
  • またdownload('/logvol1.txt',"log.txt")のように,file_from/を頭につける必要がありました.これでアプリケーション用フォルダの作業フォルダ(直下)を参照できます.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import dropbox

class TransferData:
    def __init__(self, access_token):
        self.access_token = access_token

    def upload_file(self, file_from, file_to):
        """upload a file to Dropbox using API v2
        """
        dbx = dropbox.Dropbox(self.access_token)

        with open(file_from, 'rb') as f:
            dbx.files_upload(f.read(), file_to,mode=dropbox.files.WriteMode.overwrite)
    
    def download_file(self,file_from,file_to):
        """download a file to Dropbox using API v2
        """
        dbx = dropbox.Dropbox(self.access_token)

        with open(file_to, 'rb') as f:
            dbx.files_download_to_file(file_to, file_from)
    


def upload(file_from,file_to):
    access_token = "ほげほげ" # アプリケーション用フォルダへのアクセストークン
    transferData = TransferData(access_token)
    # API v2
    transferData.upload_file(file_from, file_to)

def download(file_from,file_to):
    access_token = "ほげほげ" # アプリケーション用フォルダへのアクセストークン
    transferData = TransferData(access_token)
    # API v2
    transferData.download_file(file_from, file_to)

if __name__ == '__main__':
    # upload()
    download('/logvol1.txt',"log.txt")

おわりに

  • 今回は天鳳ログのスクレイプまででした.
  • 参考にさせていただいた先人様達ありがとうございます.
  • 次回はLINE botの応答部分について書こうと思います.
  • 僕自身プログラミングは本業でないため,書き方やテクニック等で「もっとこうしたほうがいいよ!」「これじゃセキュリティがよくないよ!」等ありましたらぜひコメントください!!!!

p.s.

[2023.5.9] Herokuが有料になりましたので,その旨追記するとともに,少しだけ文章直しました.最近は天鳳もリア麻もやらなくなりました.

13
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
14