スクレイピングして前処理してpostgreSQLに書き込む

野球のデータを使って分析したいなと思っていつも「サイトの必要な部分をコピーして、excelに張り付けてcsvにして保存」をしていました。がしかし、そろそろ面倒だなと思い、「スクレイピングして前処理してpostgreSQLに書き込む」までをやってみました。

# 必要なライブラリのインストール

## スクレイピングなど
```py

import pandas as pd
from bs4 import BeautifulSoup
import datetime
```

- https://www.crummy.com/software/BeautifulSoup/bs4/doc/

## postgreSQL書きこみ

```py
import psycopg2
from sqlalchemy import create_engine
```
- psycopg2
 - https://www.psycopg.org/docs/install.html#installation
- sqlalchemy
 - https://docs.sqlalchemy.org/en/13/core/engines.html

# データの取得
今回は巨人の打者の成績データを「pandas.read_html」で取得します。
ページの表(tableタグ)を順番取ってきてdataframeのリストが返ってくるようです。

- pandas.read_html
 - https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_html.html

```py
raw_data = pd.read_html("https://baseball-freak.com/audience/giants.html", flavor="bs4")
```

リストが返ってくるので、こんな感じになります
![スクレイピング結果.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326771/b59a07d6-1ec0-7fad-b5cb-b092814ef359.jpeg)

1つ目のdataframeがこんな感じ。
![1つめのdataframe.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326771/72575def-22e1-3d88-792f-0895e6f34e18.jpeg)

2つ目に今回ほしいデータが入っているはずなのでそれを確認

```py
data_set = raw_data[1] # ページ内に複数の表があり、今回ほしいのは2つ目の表なので[1]と指定します
data_set
```
![dataset.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326771/77b600dd-d06c-75f2-7ffd-93c7aa5ee4cc.jpeg)


# データフレームの内容確認
取得したデータの中身を確認します。

```py
pd.concat(
    [data_set.count().rename('件数'),
    data_set.dtypes.rename('データ型'),
    data_set.nunique().rename('ユニーク件数'),
    data_set.isnull().sum().rename('null件数'),
    (data_set.isnull().sum() * 100 / data_set.shape[0]).rename('欠損の割合 (%)').round(2)],
    axis=1
)
```
![データ調査.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326771/e19d3aa3-b01a-2aa5-075c-2d1fe8c70e91.jpeg)


全部がobjectになっているのがわかります。また、先発投手、観客数、試合時間はnullが含まれるようです。


この中で日付、スコア、観客数について型変換などをして分析できるようにします。

# 日付処理
- 日付カラムを月と日のカラムに分ける
- 日付カラムを日付型にする
- 曜日を「dt.dayofweek」で算出する
 - https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.dayofweek.html

```py

data_set["月"] = data_set["日付"].replace("月.*$", "", regex=True) # regex:正規表現
data_set["日"] = data_set["日付"].replace("日.*$", "", regex=True).replace("^.*月", "", regex=True)

for i in range(len(data_set['月'])):
    data_set['日付'] = "2019/" +  data_set["月"] + "/" + data_set["日"]
    
data_set['日付'] = pd.to_datetime(data_set['日付'])
data_set['曜日（数）'] = data_set['日付'].dt.dayofweek
data_set.head()
```
![日付.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326771/6a37530d-2b3e-8510-ff7a-fb22faece2a2.jpeg)


# 観客数の処理
- 「,」と「人」をreplace
- fillnaでNaNを0で埋める
- 型を「int」に変更する

```py
data_set["観客数"] = data_set['観客数'].replace(',', '', regex=True).replace(' 人', '', regex=True).fillna('0').astype(int)
data_set
```
![観客数.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326771/32bb1718-c207-ba6f-1f7d-3a06ab07e3ad.jpeg)


# スコアの処理
- str.splitでスコアを二つに分ける
- カラムが2つに分かれるので、カラム名を付けて、int型に変換

```py
data_set_score = pd.concat([data_set, data_set['スコア'].str.split(' - ', expand=True)], axis=1)
data_set_score_rename =data_set_score.rename(columns={0:'得点', 1:'失点'})
data_set_score_rename['得点']=data_set_rename['得点'].replace('中止', 0).astype(int)
data_set_score_rename['失点']=data_set_rename['失点'].fillna(0).astype(int)
```

# postgreSQLへの接続
```py

connection_config = {
    'user': '*****',
    'password': '*****',
    'host': '*****',
    'port': '****', # なくてもOK
    'database': '*****'
}

engine = create_engine('postgresql://{user}:{password}@{host}:{port}/{database}'.format(**connection_config))

```

# postgreSQLへ保存
- DataFrame.to_sql
 - https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_sql.html


```py
data_set_score_rename.to_sql('giants',schema='baseball_2019', con=engine, if_exists='replace', index=False)
```

個人的にはここでつまづきました。何につまづいたのかというと、スキーマ名を指定する方法がわからなかったです。公式ドキュメントを読んですぐに解決しましたが、解決するまで1時間くらいかかりました。わからないときってほんと、わかんないですよねー。

以上になります！記載内容で間違いなどありましたら、優しく教えていただけると嬉しいです！