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

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

# 概要
## アプリ概要チャート
* アプリは主に以下の2つの機能からなります．  
    1. 天鳳のログを自動でダウンロードし保存する（図の紫矢印）
    2. ユーザ（LINE）のリクエストに応じて集計情報を返信する（図の緑矢印） 
* 本記事では項目1に関する部分について説明します．項目2については別の記事にします．
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160678/15131380-c118-3585-6af3-0acc1b43b52f.png)


## この記事の続き
* [天鳳個室の自動成績管理アプリをLINE botとPythonで作る　その②](https://qiita.com/all_green/items/7d33ad7472a7a3d4aba6)
* [天鳳個室の自動成績管理アプリをLINE botとPythonで作る　その③](https://qiita.com/all_green/items/9de873bac7fe72264f67)


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

![ui.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160678/86b61b14-f645-6a9f-9c98-8579e9c61268.png)





# 使用言語・ツール・サービス・モジュール等
## 使用言語
* Python 3.7.3   

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

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

* [Herokuホーム](https://jp.heroku.com)
* web上でプログラムを実行するためのサービス
* 今回はLINE botの実行アプリとログスクレイピングスクリプトの2つの実行環境として利用


### LINE bot (LINE Messaging API)
* [LINE Messaging API 公式サイト](https://developers.line.biz/ja/docs/messaging-api/overview/#messaging-apiの仕組み)
* 作成したLINE botをPythonで操作できる
* UIとして今回はLINEを選んだ．


### 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/)に書かれている方法で行いました．
具体的には，

"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](https://qiita.com/seigo-pon/items/ca9951dac0b7fa29cce0)を参考にしました．
![スクリーンショット 2020-06-02 0.34.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160678/a5b44360-139a-432e-c06d-d248562e0885.png)


## コード例
### プリアンブル部
モジュール類はAnaconda環境であれば全部デフォルトで入っているはずです．　　
- ないものがあればpipやcondaでインストールしてください．

download4は自作モジュールで，Dropboxとのファイルのやり取りに使います（詳細は後で説明します）．　　 

```python
# 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
```
### 関数の定義
可読性を上げるために，スクレイプ実行部分を関数化しておきます．

下のサイトにあるコードを使わせていただきました：
- [PythonでWeb上の画像などのファイルをダウンロード（個別・一括） -note](https://note.nkmk.me/python-download-web-images/)

```python
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）の分のログだけ書き出すようにしています．


```python
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```は```/```を頭につける必要がありました．これでアプリケーション用フォルダの作業フォルダ（直下）を参照できます．
* 実装にあたっては以下のサイトを参考にしました
    * [python scriptからDropboxにファイルをアップロードする](https://www.it-swarm.dev/ja/python/python-scriptからdropboxにファイルをアップロードする/1047611873/)
    * [Dropbox APIを使ってDropboxのフォルダを操作する（調査中）](https://qiita.com/TakamiChie/items/6b21652afa7018ae6b27)

```python
#!/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")
```
 


* あとはこれらのファイルをHerokuにpushして定期自動実行すれば，Dropbox上のログファイル（```logvol1.txt```）が毎日更新されていきます．
* Herokuの利用，設定等はこちらを参考にしました．　　
    * [初心者向け】PythonによるHeroku環境で簡単LINEBot開発](https://miyabi-lab.space/blog/21)
    * [Herokuでお天気Pythonの定期実行 -Qiita](https://qiita.com/seigo-pon/items/ca9951dac0b7fa29cce0)


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



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