ボートレースの結果のファイルをスクレイピングでとってくる

# 目的
ボートレースの機械学習を行うため以下のリンクに載っている各種ダウンロードの**競争成績ダウンロード**と**番組表ダウンロード**の二つのデータを集めます。

https://www.boatrace.jp/owpc/pc/extra/data/download.html

ただこの二つをダウンロードするためには開催された`年、月、日付け`を**手動で選択する必要があり**、数年分のデータをダウンロードするとなるとかなり手間になってします。
そこでダウンロードする作業を**selenium**を使って自動化してみます。
またダウンロードするデータは`lzh`という圧縮ファイル形式になっているので解凍して別のフォルダの保存する作業も自動化します。



# 実装環境
* Windows10
* Anaconda
* Python=3.7.13
* VScode

# 環境構築
今回環境構築ではAnacondaをつかって行います。

```:Anaconda Prompt
conda create -n selenium
```
```:Anaconda Prompt
conda activate selenium
```
`selenium`という名前の仮想環境を構築し、`conda activate`で仮想環境を有効にします。
次に必要なライブラリをインストールします。

**追記:seleniumのバージョン4.3.0から`find_element_by`系統のメソッドが削除されたのでインストールする際は4.3.0以前のものをバージョン指定して下さい。**

```:Anaconda Prompt
conda install selenium
```
```:Anaconda Prompt
pip install lhafile
```
```:Anaconda Prompt 
pip install webdriver_manager
```
今回使うライブラリは`selenium` `lhafile` `webdriver_manager`の３つです。`selenium`以外はcondaにはなかったので、pipを使ってインストールしています。

ちなみに...
* `selenium`はPythonで自動化やスクレイピングをするためのライブラリです。
* `lhafile`は`lzh`形式のファイルの読み書きをするためのライブラリです。
* `webdriver_manager`はChromeを最新バージョンにしてから立ち上げるライブラリです。これを使うことで、バージョンが異なる際に生じるエラーを回避することができます。詳しくはこの[リンク](https://solomaker.club/how-to-download-webdriver-manager-for-python/)を見てみてください。

環境構築は以上になります。

# コーディング
今回はファイルのダウンロードをする処理と、ファイルを解凍する処理をそれぞれ関数にして作ります。

## ファイルダウンロード編
ここでは引数を`ダウンロード先のパス` `ダウンロード元のサイトのURL` `取得するデータの期間(月毎)` の3つの関数を作っていきます。
処理全体のコードは以下のようになります。

```python:scraping.py
# 必要なモジュールのインポート
from selenium import webdriver
import time
from selenium.webdriver.support.select import Select
from webdriver_manager.chrome import ChromeDriverManager

def kyoutei_data_download(download, url, month):
    options = webdriver.ChromeOptions()
    options.add_experimental_option("prefs", {"download.default_directory": download })
    browser = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=options)
    browser.implicitly_wait(3)
    url = url
    #urlからwebページに移る
    browser.get(url)
    #処理を3秒間止める
    time.sleep(3)

    for i in range(month):
        #HTMLのframeを選択して移る
        browser.switch_to.frame('menu')

        #ダウンロードしたい月の項目を選択
        dropdown = browser.find_element_by_name('MONTH')
        select = Select(dropdown)


        #ご覧になりたい日付けのところを任意のところに選択できる
        select.select_by_index(i+1)
        #デフォルトのフレームに移る
        browser.switch_to.default_content()
        #３秒間処理を止める
        time.sleep(3)
        #JYOUというnameをしているframeに移る
        browser.switch_to.frame("JYOU")
        
        #日付ごとに選択してダウンロードする処理
        for j in range(len(browser.find_elements_by_xpath("//*[@type='radio']"))):     
            time.sleep(6)
            #radioボタンの選択（日にちの選択）
            browser.find_elements_by_xpath("//*[@type='radio']")[j].click()

            browser.find_elements_by_xpath("//*[@value='ダウンロード開始']")[0].click()

        browser.switch_to.default_content()    

        time.sleep(2)

    browser.close()
```

## 各コードの処理内容
各コードで何をしているのかを解説してみます。

## Chromeの設定と立ち上げ処理

```python
options = webdriver.ChromeOptions()
```
ここでは立ち上げるブラウザの設定を行うためのインスタンスを生成しています。

```python:
options.add_experimental_option("prefs", {"download.default_directory": download })
```
ここではブラウザ上でダウンロードしたデータの保存先を設定しています。
ダウンロード先のパスである引数の`dawnload`を指定しているのでユーザーが指定したフォルダに保存することができます。

```python:
browser = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=options)
```
ここでは立ち上げるChromeの実行ファイルと先ほど設定した内容を反映させています。
実行ファイルの引数に`ChromeDriverManager().install()`を指定することで、常に最新バージョンのChromeの実行ファイルで立ち上げることができます。
詳しくは以下のリンクを参考にしてください。

https://qiita.com/centerfield77/items/0aebd6ed452b278d872c

```python:
browser.implicitly_wait(3)
```
ここでは要素が見つかるまでの待機時間を指定しています。
詳しくは以下のサイトを参考にしてみてください。

https://www.fenet.jp/dotnet/column/language/python/7851/

## for文内の処理
for文の中では月を選択した後に日にちを一日ずつ選択したらダウンロードボタンを押してダウンロードする処理を行っています。
２か月以上のデータを取得する場合は、すべての日にちのダウンロードが終わったらひとつ前の月を選択して同様の処理を行っています。
コードの中にはサイト内で使われている単語があったりするのでF12などを押してデベロッパーツールを起動してサイトのマークダウンを確認しながら解説していきます。

今回は番組表のサイトのマークダウンを見ていきます。（結局競争成績も同じ作りでした）
![スクリーンショット 2022-05-11 103734.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1878034/4b301780-6085-8609-7f4e-be2851367f00.png)

マークダウンを見ていくと二つのHTMLファイルが`frame`タグで指定されています。name属性はそれぞれ`menu`と`JYOU`です。操作していくとわかりますが、`memu`のHTMLファイルは月指定する場所になります。`JYOU`は初期の状態では何もありませんが月を選択した後に出てくる日付け選択の項目の場所になります。
今の状態のままだと二つのHTMLファイルがある状態なので選択もクリックもできないので、それぞれのファイルに移動する必要があります。移動せずに操作を行うとエラーが出ます。

```python:
browser.switch_to.frame('menu')
```
`switch_to.frame`メソッドを使うことで指定した`name`属性のHTMLファイルのフレームに移動します。移動したらようやく選択項目を操作することができます。実装してみて一番わかりずらいエラーでした。以下のリンクで同じエラーの方がいたので参考までにしてください。

https://hacknote.jp/archives/51263/

```python:
dropdown = browser.find_element_by_name('MONTH')
select = Select(dropdown)
```
ここでは月の選択項目を選ぶ処理をしています。今回は`MONTH`という`name`属性を指定しています。

```python:
select.select_by_index(i+1)
```
月の項目を選択したら実際どの月を選ぶのかを指定しています。`i`はfor文の中にあるのでユーザーが指定した期間だけ選択することになります。`i+1`としているのは、月の選択項目の初めの選択肢がデフォルトで設定されている`(月を設定してください)`となっているためそれを省くためです。
これで月の選択は以上です。

```python:
browser.switch_to.default_content()
time.sleep(3)
```
`switch_to.default_content()`を使いひとつ前のフレームに戻ります。これを行わないと日付け選択の部分である`JYOU`に移動することができません。`time.sleep(3)`で処理を３秒間止めています。月を指定してから日付けが表示されるまで若干時間がかかるので表示されるまでの待機時間として処理を加えています。

```python:
browser.switch_to.frame("JYOU")
```
日付けの項目が表示されたら`name`属性が`JYOU`のフレーム移動します。

ここからは日にちごとにダウンロードを行っていきます。

```python:
len(browser.find_elements_by_xpath("//*[@type='radio']"))
```
for文で範囲指定する部分では上記のようなコードを指定しています。ここでは端的に言えばラジオボタンが何個あるのかを数えています。ラジオボタンの数だけ処理を繰り返すためこのような処理をしています。
`browser.find_elements_by_xpath("//*[@type='radio']")`この部分でラジオボタンのリストを取得することができます。
以下を参考です。

https://code-examples.net/ja/q/1455984

```python:
time.sleep(6)
browser.find_elements_by_xpath("//*[@type='radio']")[j].click()
browser.find_elements_by_xpath("//*[@value='ダウンロード開始']")[0].click()
```
ここでは実際に日付けを１日から選択してダウンロードボタンを押してダウンロードをしています。ラジオボタンの選択はリストのインデックス番号を指定することで選択できます。ダウンロードボタンは一つの項目しかないのでインデックス番号は`0`で固定されています。
`time.sleep(6)`で処理を６秒間止めていますが、これはダウンロードボタンを押してからダウンロードされるまでの間にラグがあるため日付け選択の処理の間に処理を加えています。

```python:
browser.switch_to.default_content()    
time.sleep(2)
```
最後にひとつ前のフレームに移動して処理を２秒間止めて、`browser.close()`でウェブドライバを閉じて一連の処理を終了します。
以上がファイルをダウンロードする関数の解説でした。

## ファイル解凍編
`lzh`ファイル解凍処理は以下のリンクを参考にして作ります。
詳しい内容については以下のリンクを参考にしてください。

https://teimon.jp/colab_de_go_4/

ここでは引数が`解凍したいファイルのパス` `解凍したファイルを保存するファイルのパス` の関数を作っていきます。


```python:scraping.py
# 必要なモジュールのインポート
import re
import lhafile
import os

def data_kaitou(kyoutei_download_folder,kaitou_folder):
    os.makedirs(kaitou_folder, exist_ok = True)
    lzh_file_list = os.listdir(kyoutei_download_folder)
    for lzh_file_name in lzh_file_list:

        file = lhafile.Lhafile(os.path.join(kyoutei_download_folder, lzh_file_name))

        # 解凍したファイルの名前を取得
        info = file.infolist()
        name = info[0].filename

        # 解凍したファイルの保存
        open(os.path.join(kaitou_folder ,name), "wb").write(file.read(name))

```


## まとめる
上記で作った二つの関数をまとめます。
ここでは`input`関数で`ダウンロード先のフォルダのパス` `解凍したファイルの保存先のパス` `集めたい競艇データのURL` `集めるデータの期間(半角)`を受け取るようにします。
処理が終わったら`すべての処理が完了しました`という文字を出力して終了です。

```python:scraping.py
if __name__ == '__main__':
    #必要な項目の入力
    download_folder = input('ダウンロードするファイルのパスを指定してください')
    kaitou_folder = input("解凍したファイルの保存先のパスを指定してください")
    url = input("集めたい競艇データのURLを指定してください")
    manth = int(input("何か月分のデータを集めるか指定してください(半角)"))

    #データのダウンロード処理
    kyoutei_data_download(download_folder, url, manth)

    #圧縮ファイルの解凍処理
    data_kaitou(download_folder, kaitou_folder)

    print('すべての処理が完了しました')
```

# 実行
今回作成したプログラムを実際に動かしてみます。
URLは以下のリンクをコピーして使ってみてください。
番組表のURL：`http://www1.mbrace.or.jp/od2/B/dindex.html`
競争成績のURL：`http://www1.mbrace.or.jp/od2/K/dindex.html`
パスの入力はAnacondaPromptに指定のファイルをドラックアンドドロップする方法が簡単です。

* 初めに作成した`selenium`という仮想環境を有効にする
```:Anaconda Prompt
conda activate selenium
```
* Pythonの実行ファイルがある階層までディレクトリ移動する。
```:Anaconda Prompt
cd ...
cd ...
cd ...
```

* Pythonファイルを実行
```:Anaconda Prompt
python scraping.py
```

入力項目に値を入力（パス入力はファイルをドラックアンドドロップ）
すべての処理が終わったら以下のような画面になります。
![スクリーンショット 2022-05-11 012112.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1878034/d4236f56-e7bd-787f-c177-a912ba322586.png)

保存先のファイル保存されていたら成功です。
2022年5月分のデータをダウンロードした結果以下のように保存することができました。

ダウンロードしたファイル↓
![スクリーンショット 2022-05-11 012217.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1878034/98cb243d-3a04-dec0-79f2-52fbd90ce13f.png)

解凍したファイル↓
![スクリーンショット 2022-05-11 012247.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1878034/d8aabb8e-2b0f-2273-3d4f-ff7e56be5e70.png)


# 最後に
見ていただいてありがとうございました。実際に実装してもらうとわかりますが10年分のデータを集めようとすると30分以上はかかってしまいます。`time.sleep`がボトルネックになっている感はあります。すべて手探りで調整したものなので最適ではないと思われます。サイトのほうでも選択してから日付けが表示されるまでに時間かかっているので手作業でやるとストレスがかかります。一度実行したらあとは勝手にやってくれる自動化のほうがストレスフリーなので結果的に自動化してよかったです。