[Python]スクレイピングで情報取得→googleカレンダーAPIでイベント登録を自動化

# はじめに
■やってみたこと
Webスクレイピングにより取得した情報をpandasで整形/csv化し、GoogleCalendarAPIを使ってイベント登録するまでを自動化してみました。
Python初心者且つqiitaも初投稿なので稚拙ですが、自分への備忘も兼ねてまとています。
コードは[Github](https://github.com/deatiger/IPO_Auto_Scheduler)に置いています。

■きっかけ
[Python Boot Camp@埼玉](https://pyconjp.connpass.com/event/69433/)に参加してWebスクレイピングに触れたので、何か身の回りで便利にできることがないかなと模索した結果から。
1年程前からIPOの抽選に応募しており(未だ当選はないが・・・)、スケジュール確認の手間を省きたいと考えました。

# 目次
1.pandasでDataFrameを作成
2.スクレイピングパラメータの選定
3.DataFrameをcsv化
4.GoogleCalendarAPIを使う準備
5.API呼び出し関数定義とパラメータ修正
6.main()関数の定義
7.リファレンス

#1. pandasでDataFrameを作成
pandasはスクリプト序盤でimportしておきます。

```python:IPO_scheduler.py
import pandas as pd
```
DataFrameを以下のように作成。正直'companyname'と'bookbill_term'以外のカラムはタイミングによって正確に取得できないので使えない。使わない。が一応定義しておきます。

```python:IPO_scheduler.py
df = pd.DataFrame(columns=['companyname', 'bookbill_term', 'conditional_determined_date', 'price', 'unit', 'listing_date'])
```

#2. スクレイピングパラメータの選定
scraping()関数で定義している内容について書きます。
スクレイピング用スクリプトは[Python Boot Campチュートリアルテキスト](http://bootcamp-text.readthedocs.io/textbook/7_scraping.html)として配布されたものを流用。

今回のスクレイピング対象URLはこちら。
[SBI証券-新規上場・公募売出](https://www.sbisec.co.jp/ETGate/?OutSide=on&_ControlID=WPLETmgR001Control&_DataStoreID=DSWPLETmgR001Control&burl=search_domestic&dir=ipo%2F&file=stock_info_ipo.html&cat1=domestic&cat2=ipo&getFlg=on&int_pr1=150313_cmn_gnavi:6_dmenu_04)

最終目標はGoogleCalendarにイベントを登録することなので、最低限欲しい情報は「会社名」と「ブックビル期間」。
ページソースのhtmlとにらめっこした結果、不要な情報も取得するイケてない作りですが以下のように設定しました。

会社名の抽出

```python:IPO_scheduler.py
#IPO予定の会社名リストを取得
companies = soup.find_all('div', class_='thM alL')
for company in companies:
    name = company.find('p', class_='fl01').text
    company_list.append(name)
#companynameカラムに情報を入力
df.loc[:, 'companyname'] = company_list
```

ブックビル期間などその他情報の抽出

```python:IPO_scheduler.py
#各会社のIPO情報を取得
informations = soup.find_all('div', class_='accTbl01')
i = 0
for information in informations:
    #IPO情報を記述するレコードを作成
    info_list = []
    div_info = information.find_all('div', class_='tdM')
    for div in div_info:
        try:
            info = div.find('p', class_='fm01').text
            info_list.append(info)
        #valueがnoneとなる抽出対象はtextアトリビュートでエラーとなるので、例外をpassする。
        except:
            pass
    #1社ごとに情報をDataFrameへ反映する。
    df.iloc[i, 1:5] = info_list
    #次の行へ書き出す為にカウントアップ
    i = i +1
```

#3. DataFrameをcsv化
format_csv()関数にて定義している内容について書きます。
osモジュールで各csvファイルのパスを定義しておきます。
尚、スクリプト実行ディレクトリ配下にcsv格納用ディレクトリがあるものとします。

```python:IPO_scheduler.py
csv_file_dir = os.path.join(os.getcwd(), 'csvfiles')
csv_file_path = os.path.join(csv_file_dir, 'output.csv')
```

空ファイルを作成し、スクレイピング結果を格納している変数dfをto_csvメソッドでcsv化します。

```python:IPO_scheduler.py
#書込用ファイルの作成
f = open(csv_file_path, 'a')
f.close()
#scraping()で取得したdfをcsv書き出し
df.to_csv(csv_file_path)
```

このcsvファイルを元にGoogleCalendarAPIに情報を渡してイベント登録するわけですが、初回実行ならこれだけで良いです。
２回目以降は前回登録したイベントを再登録しないよう重複排除したいので、新旧比較します。

```python:IPO_scheduler.py
    old_csv_file = os.path.join(csv_file_dir, 'output_old.csv')
    temp_csv_path = os.path.join(csv_file_dir, 'temp.csv')
    #新旧比較し重複分を除いたDataFrameを作成する
    current_df = pd.read_csv(csv_file_path, index_col=0)
    if os.path.exists(old_csv_file):
        old_df = pd.read_csv(old_csv_file, index_col=0)
        #旧世代のcsvファイルからcompanynameカラムの値をリストとして取得
        old_list = old_df.loc[:, 'companyname'].values
        #old_list内の値と一致するcompanynameを持つ行を除外する
        for company_name in old_list:
            current_df = current_df[~current_df['companyname'].str.contains(company_name)]
    else:
        pass
    current_df.to_csv(temp_csv_path)
    return temp_csv_path
```

temp.csvが実際にイベント登録対象の情報となります。
この間に旧ファイル削除なり世代管理していますが割愛。コード参照。

#4. GoogleCalendarAPIを使う準備
[Google本家のチュートリアル](https://developers.google.com/google-apps/calendar/quickstart/python)に従い、APIを使えるようにしておきます。
日本語での説明は[こちら](https://dev.classmethod.jp/cloud/google-calendar-api-get-start/)。

###筆者がハマった箇所
**※AuthorizationのScopeに注意**
Googleのサンプルスクリプトはイベントの一覧取得のみなので```https://www.googleapis.com/auth/calendar.readonly```で定義されている。
今回実装するのはイベント登録なのでカレンダーの読み書きができるよう、以下のように変更する。
```https://www.googleapis.com/auth/calendar```

しかし一度サンプルスクリプトを実行しGoogleアカウント側でreadonly権限を承認した場合、Googleアカウント側から権限を削除したのち、read/write権限に変更後のスクリプトを実行し再度権限を承認する必要がある。
筆者はGoogleアカウント側の承認権限を変えられておらず永遠に以下の権限エラーが出ていた。。。

```python
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://www.googleapis.com/calendar/v3/calendars/primary/events?alt=json returned "Insufficient Permission">
```

[リンク：Googleアカウント-他のアプリに許可したアクセス権を取り消す](https://www.ajaxtower.jp/googleaccount/security/index4.html)

#5. API呼び出し関数定義とパラメータ修正
クレデンシャル取得関数get_credential()はGoogleのサンプルをそのまま使用しました。

イベント登録情報のbody部分はデータの整形が必要だったのでcreate_api_body()関数として定義しました。
イベント登録に関するパラメータは[本家ドキュメント](https://developers.google.com/google-apps/calendar/v3/reference/events/insert)を参照。

dateのstart/endパラメータのフォーマットが"yyyy-mm-ddThh:mm :ss-hh:mm"なので、
スクレイピングで取得した日時の値を適宜修正する。（まどろっこしい・・・）

```python:IPO_scheduler.py
    now = datetime.datetime.now()
    #開始日時フォーマットの整形
    start_time = values['bookbill_term'].split('〜')[0]
    start_time = re.split('[^0-9/:\s:]', start_time)[0]
    start_time = str(now.year) + '/' + start_time
    start_time = start_time.replace('/', '-')
    start_time = start_time.replace(' ', 'T', 1)
    start_time = start_time + ':00+09:00'
    #終了日時フォーマットの整形
    end_time = values['bookbill_term'].split('〜')[1]
    end_time = re.split('[^0-9/:\s:]', end_time)[0]
    end_time = str(now.year) + '/' + end_time
    end_time = end_time.replace('/', '-')
    end_time = end_time.replace(' ', 'T', 1)
    end_time = end_time.replace(' ', '')
    end_time = end_time + ':00+09:00'
```

あとは本家ドキュメントの書き方に従ってパラメータを入れているだけです。

#6. main()関数の定義
main()関数ではこれまで書いてきた関数を呼び出し、適宜引数を渡してやって・・・ということをしています。
temp.csvを再度DataFrame化して1行ずつ処理させる箇所が以下です。

```python:IPO_scheduler.py
    event_df = pd.read_csv(temp_csv_path, index_col=0)
    for i, values in event_df.iterrows():
        try:
            #予定csv件数繰り返し
            body = create_api_body(values)
            #API呼び出し
            event = service.events().insert(calendarId='primary', body=body).execute()
```

calendarId='primary'は現在ログインしているユーザのカレンダーをいじる魔法の言葉です。

#7. リファレンス
[Qiita:CSVのデータをPythonでGoogleカレンダーに一括登録する（Google Calendar API）](https://qiita.com/ping2shi2/items/6cf5ba9f148898b539bc)
実行ユーザの切替など、私よりかなり高度なことをしていらっしゃいます。
スクリプトの構成やlogger関数などたくさん~~パクらせて~~参考にさせていただきました。
[Google本家ドキュメント:Events:insert(イベント登録のパラメータやbodyの書き方など)](https://developers.google.com/google-apps/calendar/v3/reference/events/insert)
[Google本家ドキュメント:Python Quickstart(GoogleCalendarAPIのチュートリアル)](https://developers.google.com/google-apps/calendar/quickstart/python)
[Googleドキュメントの日本語版(キャプチャ付き)](https://dev.classmethod.jp/cloud/google-calendar-api-get-start/)