Pythonで競艇のデータ分析をやりたいため、データを正規化するコードを書いた

## 背景
今年度、メトリクスに関して学んだのもあって、いろいろデータ分析を試してみたい衝動に駆られている。そんなこと思いながらネットサーフィンしていると、競艇のデータは公式がまとめてくれてるので、簡単そうな気がした。

## ここで、できること
以下のような、テーブルが出来上がる。
|nIndex|場所|日付|題名|第|何日目|空白列|年月日|
|:--|:--|:--|:--|:--|:--|:--|:--|
|1|芦屋|3/16|ＤММボートちゃんね|第|6日||24-03-16|
|2|下関|3/16|ヴィーナスシリーズ第|第|5日||24-03-16|
|3|丸亀|3/16|大阪スポーツカップ|第|4日||24-03-16|
|4|尼崎|3/16|第２２回報知ローズカ|第|4日||24-03-16|
|5|住之江|3/16|ボートピア梅田開設１|第|2日||24-03-16|

テーブル構造は、こんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3741886/9738d872-a37c-5ad6-ef9d-3977284940d2.png)

## コード
勢いで書いてみた。こんな書き方いかんやろーって思いつつ、勢いなので。。

公式から落とせる競艇データは、lzh形式で圧縮されているので、下記リンクと同じやり方で解凍している。
https://qiita.com/Carl_Jonson/items/27d5ed344bd8e9ed4c2b

```python
# 必要なライブラリをインポート
import urllib.request
import shutil
from datetime import datetime, date, timedelta
import subprocess
import re
import os
import csv

# 途中で変わっちゃったりしないように、今日の日付を配列に入れる
dt_today = datetime.today()

# テーブル化する情報を格納する配列を用意する、一緒にヘッダだけ入れる
maindat = [["nIndex", "場所", "日付", "題名", "第", "何日目", "空白列", "年月日"]]
racedat = [["ParentnIndex", "rIndex", "空白列", "レース番号", "レース名", "距離", "天気", "風", "風向", "風速", "波", "波の高さ", "進入固定フラグ", "決まり手"]]
playdat = [["ParentrIndex", "空白列", "着順", "艇", "登録番号", "指名", "モーター", "ボート", "展示タイム", "進入", "スタート", "タイム"]]
oddsdat = [["ParentrIndex", "空白列", "賭け式", "的中番号", "配当", "人気", "人気順"]]

# どれだけの期間データを取るか指定する
syutokukikan = 90

# 出力先のファイルを定義する
export_maindat = "main.csv"
export_racedat = "race.csv"
export_playdat = "play.csv"
export_oddsdat = "odds.csv"

# maindatテーブルのIndexを作成
m_index = 0
r_index = 0

# 必要な日数分、ループ
for count in range(1, syutokukikan):

    # count 日前の日付を引き算して生成
    calcdate = dt_today - timedelta(days = count)
    
    # 生成した日付を使い、urlを生成
    tgturl = 'https://www1.mbrace.or.jp/od2/K/' + calcdate.strftime('%Y%m') + '/k' + calcdate.strftime('%y%m%d') + '.lzh'
    
    # ついでにファイル名も生成
    gfilename = 'k' + calcdate.strftime('%y%m%d') + '.lzh'
    tfilename = 'k' + calcdate.strftime('%y%m%d') + '.txt'

    # urllib.requestを使ってダウンロードする
    urllib.request.urlretrieve(tgturl, gfilename)
    
    # 7zipを使って、解凍をする
    subprocess.run("7z.exe e " + gfilename)

    # テキストファイルを読み込む
    f = open(tfilename, 'r')
    textdata = f.readlines()

    # 行数分ループする、カウンタも取得する
    for ncnt, singledata in enumerate(textdata):

        if '［成績］' in singledata:

            # ［成績］という文字が含まれる場合、場所とかを示していると判断する

            # スラッシュと半角スペースが重なっている場合、その直後の半角スペースを消す
            buf = singledata.replace('/ ', '/')
            
            # ［成績］の文字はいらんので消す
            buf = buf.replace('［成績］', '')
            
            # 全角スペースを消す
            buf = buf.replace('　', '')

            # 連続する半角スペースを一つにする
            buf = re.sub(r'\s+', ' ', buf)

            # indexを足す
            m_index = m_index  + 1
            buf = str(m_index) + " " + buf

            # 日付を足す
            buf = buf + " " + calcdate.strftime('%y-%m-%d')

            # スプリットして配列にして、格納する
            maindat.append(buf.split(" "))

        elif len(singledata) > 10 and len(singledata.replace('-', '')) == 1 :

            # -------------------------- って行を区切りとして識別する

            # 全角スペースを消す
            buf = textdata[ncnt - 2].replace('　', '')

            # 侵入固定の文字が含まれる場合、文字消して末尾に追加する
            if '進入固定' in buf:

                buf = buf.replace('進入固定', '') + " " + '進入固定'

            # 風速10m以上だと区切れなくなるので、無理やりスペース足す
            pprr = re.match(r'.*[1-9][0-9]m', buf)
            if pprr:
                buf = buf[:pprr.span()[1] - 3] + " " + buf[pprr.span()[1] - 3:]
                
            # 2つ上の配列にあるレース情報の連続する半角スペースを一つにする
            buf = re.sub(r'\s+', ' ', buf)

            # indexを足す
            r_index = r_index + 1
            buf = str(m_index) + " " + str(r_index) + " " + buf

            # 決まり手が記載されている行のデータをとって、buffer文字に足す
            buf2 = re.sub(r'\s+', ' ', textdata[ncnt - 1])
            buf = buf + " " + buf2.split(" ")[13]

            # スプリットして配列にして、格納する
            racedat.append(buf.split(" "))

            # 選手データを、ループして抜き出す
            for pcnt in range(1, 7):

                # 全角スペースを消す
                buf = textdata[ncnt + pcnt].replace('　', '')

                # K . のデータが含まれている場合、それ以降の文字列を消す
                if 'K .' in buf: 

                    buf = buf[:buf.find('K .')]

                # .  .のデータが含まれている場合、その文字列を消す
                if '.  .' in buf: 

                    buf = buf.replace('.  .', '')

                # 連続する半角スペースを一つにする
                buf = re.sub(r'\s+', ' ', buf)

                # indexを足す
                buf = str(r_index) + " " + buf
                
                # スプリットして配列にして、格納する
                playdat.append(buf.split(" "))

            # 8行目からループしてオッズのデータを取得
            ocnt = 7

            # 3連続で「連」か「勝」の文字が出てこない場合には処理を止める、判定をするための変数を初期化
            outcnt = 0

            # 賭け式を保持しておくバッファを初期化する
            kake_buf = ""

            # 末尾までループ（念のため、止まるように）
            while ocnt < len(textdata):

                # カウンタ加算
                ocnt = ocnt + 1
                
                # 後々のことも考えてバッファに入れる
                buf = textdata[ncnt + ocnt]

                # 何も文字が無い行は、カウンタ追加してスキップする
                if len(buf) <= 1:

                    # 未検出カウンタを追加
                    outcnt = outcnt + 1

                    if outcnt >= 3:
                        
                        break

                    else:
                        
                        continue
                        
                # レース不成立の場合は、処理終了
                if 'レース不成立' in buf:

                    break

                # 賭け式が不成立の場合は、データ出力しない
                if '不成立' in buf:

                    continue
                
                # 賭け式が記載されていない場合は、ひとつ前の賭け式と同じと判断する(連と勝がない場合は、未記載と判断する)
                if not '連' in buf and not '勝' in buf:

                    buf = " " + kake_buf + " " + buf
                    
                    # 未検出カウンタを追加
                    outcnt = outcnt + 1

                    if outcnt >= 3:
                        
                        break
                        
                else:

                    # カウンタを初期化
                    outcnt = 0

                    # 賭け式の文字列を設定
                    buf2 = re.sub(r'\s+', ' ', buf)
                    kake_buf = buf2.split(" ")[1]

                # 「R」の文字が検出された場合、ループを終わる
                if 'R' in buf:

                    break
                
                # 連続する半角スペースを一つにする
                buf = re.sub(r'\s+', ' ', buf)

                # indexを足す
                buf = str(r_index) + " " + buf

                # 複勝の時だけ処理する
                if '複勝' in buf:

                    # 扱いやすいように、いったん配列に入れる
                    bufarray = buf.split(" ")

                    # 1着の複勝データの書き込み
                    p1array = [bufarray[0], bufarray[1], bufarray[2], bufarray[3], bufarray[4]]
                    oddsdat.append(p1array)

                    # 配列の要素数が少ない場合には処理しない
                    if len(bufarray) >= 7:
                    
                        p2array = [bufarray[0], bufarray[1], bufarray[2], bufarray[5], bufarray[6]]
                        oddsdat.append(p2array)
                    

                else:
                    
                    # スプリットして配列にして、格納する
                    oddsdat.append(buf.split(" "))


    # テキストを閉じる
    f.close()

    # ゴミを掃除する
    os.remove(gfilename)
    os.remove(tfilename)

# CSVで出力する main
if os.path.isfile(export_maindat):
    os.remove(export_maindat)

with open(export_maindat, 'w', newline="") as f:
    writer = csv.writer(f)
    writer.writerows(maindat)

# CSVで出力する race
if os.path.isfile(export_racedat):
    os.remove(export_racedat)

with open(export_racedat, 'w', newline="") as f:
    writer = csv.writer(f)
    writer.writerows(racedat)

# CSVで出力する play
if os.path.isfile(export_playdat):
    os.remove(export_playdat)

with open(export_playdat, 'w', newline="") as f:
    writer = csv.writer(f)
    writer.writerows(playdat)

# CSVで出力する odds
if os.path.isfile(export_oddsdat):
    os.remove(export_oddsdat)

with open(export_oddsdat, 'w', newline="") as f:
    writer = csv.writer(f)
    writer.writerows(oddsdat)
```