KaggleのKernelを通知する

# 概要
最近のKaggleのコンペティションでは、コンペ終了直前に上位者が激強Kernelや激強DiscussionでLeaderboardを荒らすことがたまにあります。

Kernelが公開されてからなるべく早く反応できるよう、Line Notifyを利用して、通知システムを作ろうと思いました。今回はu++さんの[Kaggle APIとLINE APIを用いたKernelの新規投稿を通知する仕組みの構築](https://upura.hatenablog.com/entry/kaggle-notification)を参考に、システムを構築しました。

### 通知例
<img src="https://qiita-image-store.s3.amazonaws.com/0/224333/395851a2-0480-9161-dd33-aa851263cd93.jpeg" width="450">

# 全体の流れ
- Kaggle APIを使ってKernelのリストを取得する
- 取得したKernelのリストをGoogleスプレッドシートに保存する
- 新しいKernelが増えている場合はLine Notifyで通知を行う

この記事では、コードを部分的に紹介するので、全てのコードが欲しい場合はGitHubから取っていってください。

# Kaggle API
今回はKaggleが公式で提供しているKaggle APIを使用します。使い方はGitHubのREADMEに詳しく書いてあります。
https://github.com/Kaggle/kaggle-api

`pip install kaggle`でインストールできます。

APIを使用するには、更にAPI Tokenを設定する必要があります。

Kaggleの自分のプロフィールページのAccountにある`Create New API Token`を押すとjsonファイルがダウンロードされます。
![スクリーンショット 2019-04-10 9.22.17.png](https://qiita-image-store.s3.amazonaws.com/0/224333/46bd9e2c-91f1-102a-7e1c-8ad27e9210e3.png)

ダウンロードされたjsonファイルには、自分のユーザ名とトークンが記述されています。このjsonファイルを`~/.kaggle`というディレクトリを作って入れるだけでAPIが使えるようになります。

今回使用するのは、[Kernels](https://github.com/Kaggle/kaggle-api#kernels)に関するAPIです。例えば、santanderのコンペのカーネルを新しい順に表示したい場合、以下のようなコマンドを入力します。

```
$ kaggle kernels list --competition santander-customer-transaction-prediction --sort-by 'dateCreated'

ref                                                           title                                               author              lastRunTime          totalVotes  
------------------------------------------------------------  --------------------------------------------------  ------------------  -------------------  ----------  
kanugeete/5-fold-classification-on-a-simple-neural-network    5-fold Classification on a Simple Neural network    kanugeete           2019-04-09 18:54:34           0  
darbin/logit-for-200-pca-features                             Logit for 200 PCA features                          Ilya Khristoforov   2019-04-09 06:01:56           4  
tasnimfatima/lgbm-with-no-augmentation                        LGBM with no Augmentation                           Tasnim              2019-04-09 05:55:02           1  
mhviraf/what-a-decision-tree-cannot-see                       What a decision tree cannot see?                    mhviraf             2019-04-09 06:43:10          52  
elcaiseri/simple-gnb-and-pipeline-model-with-eda              Simple gnb And pipeline Model With EDA              Kassem              2019-04-08 23:18:18           6  
manishks/kernelee24d12c85                                     kernelee24d12c85                                    manish kumar singh  2019-04-08 13:16:40           0  
sandeepkumar121995/santander-customer-transaction-prediction  Santander Customer Transaction Prediction           Sandeep Kumar       2019-04-08 11:19:28           5  
subodanirodrigo/xgboost-for-santander-customer-transaction    XGBoost for Santander Customer Transaction          Subodani Rodrigo    2019-04-08 09:59:12           0  
leonyang/blending-all                                         blending_all                                        leonyang            2019-04-09 02:04:12           0  
danmoller/funny-pattern-with-non-perfectly-round-numbers      Funny pattern with non-perfectly round numbers      Daniel Möller       2019-04-08 08:34:07           5  
kmezhoud/a-simple-xgboost-application                         A simple xgboost application                        Karim Mezhoud       2019-04-09 19:06:45           8  
vikasmalhotra08/feature-binning-using-bayesian-blocks         Feature Binning using Bayesian Blocks               Vikas Malhotra      2019-04-07 16:17:25           8  
pranaydugar/dense-net-2-gaussian-difference-features          Dense Net 2 (Gaussian Difference Features)          PranayDugar         2019-04-07 11:48:45           1  
rsaund/sant-model-check                                       sant_model_check                                    RSaund              2019-04-07 11:10:02           0  
fuhang/weighted-kernel-naive-bayes-with-lasso-elimination     Weighted Kernel Naive Bayes with Lasso Elimination  Horizon@AI酱         2019-04-08 10:41:07          56  
stevexyu/kfold-convolutional-neural-network                   Kfold Convolutional Neural Network                  Stephen             2019-04-09 15:02:48          21  
lzsraja/kernal-fe-gp-lgbm-keras                               Kernal_fe(GP)+LGBM+Keras                            Zhusong Liu         2019-04-09 18:56:53           0  
maheshak04/simpl                                              Simpl                                               MaheshKulkarni      2019-04-07 10:53:22           0  
yoheiii/bayesian-optimization-for-santandar                   Bayesian Optimization for Santandar                 YOHEI               2019-04-07 06:28:57           7  
dmacgirr/naive-bayes-normalized                               Naive Bayes - normalized                            Dan MacGirr         2019-04-06 15:31:40           0  

```

これをいい感じに整形します。ぱっと見スペース2つとかでsplitすれば良さそうなので、こんな感じで整形します。とりあえず、記事のurlとタイトルだけ保存します。

```
import subprocess
proc = subprocess.run("kaggle kernels list --competition santander-customer-transaction-prediction --sort-by 'dateCreated'", shell=True,stdout = subprocess.PIPE, stderr = subprocess.PIPE)
s = proc.stdout.decode("utf8")

kernels = []
rows = s.split("\n")

# スペース2つでlist化、先頭/末尾のスペースを削除
for i in range(len(rows )):
    row = rows[i].split("  ")
    row = [x.strip(" ") for x in row if x]
    kernels.append(row)

# ヘッダとフッタを除外
kernels = kernels[2:-1]
# 使用するパラメータ
use_params = ["url", "title"]
# 古い順にする
kernels = kernels[::-1]
# dictに変える
tmp = [{} for i in range(len(kernels))]
for i, kernel in enumerate(kernels):
    for j, param in enumerate(use_params):
        tmp[i][param] = "https://kaggle.com/" + kernel[j] if param == "url" else kernel[j]
kernels = tmp
print(kernels)
```

結果を見るといい感じに分けることができています。

```
[{'url': 'https://kaggle.com/dmacgirr/naive-bayes-normalized',
  'title': 'Naive Bayes - normalized'},
 {'url': 'https://kaggle.com/yoheiii/bayesian-optimization-for-santandar',
  'title': 'Bayesian Optimization for Santandar'},
 {'url': 'https://kaggle.com/maheshak04/simpl', 'title': 'Simpl'},
 {'url': 'https://kaggle.com/lzsraja/kernal-fe-gp-lgbm-keras',
  'title': 'Kernal_fe(GP)+LGBM+Keras'},
 {'url': 'https://kaggle.com/stevexyu/kfold-convolutional-neural-network',
  'title': 'Kfold Convolutional Neural Network'},
 {'url': 'https://kaggle.com/fuhang/weighted-kernel-naive-bayes-with-lasso-elimination',
  'title': 'Weighted Kernel Naive Bayes with Lasso Elimination'},
 {'url': 'https://kaggle.com/rsaund/sant-model-check',
  'title': 'sant_model_check'},
 {'url': 'https://kaggle.com/pranaydugar/dense-net-2-gaussian-difference-features',
  'title': 'Dense Net 2 (Gaussian Difference Features)'},
 {'url': 'https://kaggle.com/vikasmalhotra08/feature-binning-using-bayesian-blocks',
  'title': 'Feature Binning using Bayesian Blocks'},
 {'url': 'https://kaggle.com/kmezhoud/a-simple-xgboost-application',
  'title': 'A simple xgboost application'},
 {'url': 'https://kaggle.com/danmoller/funny-pattern-with-non-perfectly-round-numbers',
  'title': 'Funny pattern with non-perfectly round numbers'},
 {'url': 'https://kaggle.com/leonyang/blending-all', 'title': 'blending_all'},
 {'url': 'https://kaggle.com/subodanirodrigo/xgboost-for-santander-customer-transaction',
  'title': 'XGBoost for Santander Customer Transaction'},
 {'url': 'https://kaggle.com/sandeepkumar121995/santander-customer-transaction-prediction',
  'title': 'Santander Customer Transaction Prediction'},
 {'url': 'https://kaggle.com/manishks/kernelee24d12c85',
  'title': 'kernelee24d12c85'},
 {'url': 'https://kaggle.com/elcaiseri/simple-gnb-and-pipeline-model-with-eda',
  'title': 'Simple gnb And pipeline Model With EDA'},
 {'url': 'https://kaggle.com/mhviraf/what-a-decision-tree-cannot-see',
  'title': 'What a decision tree cannot see?'},
 {'url': 'https://kaggle.com/tasnimfatima/lgbm-with-no-augmentation',
  'title': 'LGBM with no Augmentation'},
 {'url': 'https://kaggle.com/darbin/logit-for-200-pca-features',
  'title': 'Logit for 200 PCA features'},
 {'url': 'https://kaggle.com/kanugeete/5-fold-classification-on-a-simple-neural-network',
  'title': '5-fold Classification on a Simple Neural network'}]
```

次に取得したカーネルのデータをGoogleスプレッドシートに保存します。


# Googleスプレッドシートに保存する
PythonでGoogleスプレッドシートをいじる場合、この記事がわかりやすいと思います。
https://qiita.com/akabei/items/0eac37cb852ad476c6b9
同じような手順でやってみます。

### プロジェクト作成
コンソールからプロジェクトを作成します。
https://console.developers.google.com/cloud-resource-manager
プロジェクト名は適当に`kaggle-api-notify`にしておきます。

![スクリーンショット 2019-04-10 10.12.45.png](https://qiita-image-store.s3.amazonaws.com/0/224333/ea357373-a0bd-2263-6a7b-97ea0a337691.png)

### プロジェクトにAPIを追加する
左上のナビゲーションメニューから、`APIとサービス`に移動し、Google DriveとGoogle SheetsのAPIを有効にします。

![スクリーンショット 2019-04-10 10.13.29.png](https://qiita-image-store.s3.amazonaws.com/0/224333/1d295ab6-770d-8b09-3aee-25f80a73088f.png)

### サービスアカウントキー作成
`APIとサービス`から`認証情報`に移動し、認証情報を作成、サービスアカウントキーを選択します。

![スクリーンショット 2019-04-10 10.15.26.png](https://qiita-image-store.s3.amazonaws.com/0/224333/5662d709-d0ae-fffc-3796-3bd610bbfd79.png)

サービスアカウント名を適当に決め、役割を`Project`の`編集者`にします。

![スクリーンショット 2019-04-10 20.50.24.png](https://qiita-image-store.s3.amazonaws.com/0/224333/390760ce-80ea-62e1-c59f-732ce185c825.png)

作成のボタンを押すと、アカウントの情報が記載されたjsonファイルがダウンロードされます。

このファイルで必要なのは、`client_email`の項目です。今回の場合、`kaggle-api-account@kaggle-api-notify.iam.gserviceaccount.com`のような`アカウント名@プロジェクト名.iam.gserviceaccount.com`の形式で保存されます。

```
{
  "type": "service_account",
  "project_id": "kaggle-api-notify",
  "private_key_id": "XXXXXX",
  "private_key": "-----BEGIN PRIVATE KEY-----...,
  "client_email": "kaggle-api-account@kaggle-api-notify.iam.gserviceaccount.com",
  ...
}

```

読み込んだデータを保存しておく、スプレッドシートを作成します。

https://docs.google.com/spreadsheets/create

jsonファイルから取得した`client_email`をスプレッドシートの右上の`共有`から編集者として追加します。
![スクリーンショット 2019-04-10 20.58.25.png](https://qiita-image-store.s3.amazonaws.com/0/224333/b9f41cc6-d51a-ca19-8900-6181c75da79e.png)

これでスプレッドシートの設定は完了です。

スプレッドシートをpythonで操作する場合、`gspread`をインストールする必要があります。

gspreadのリファレンス
https://gspread.readthedocs.io/en/latest/

今回使用したのは、以下のメソッドです。

| メソッド | 効果 |
|:----------:|:-----------:|
| get_all_values | 全ての値をlist形式でもらえる |
| update_acell | 単一セルの編集 |
| update_cells | 複数セルの編集 |

注：`update_cells`の引数では、編集する領域を設定し、セル単位で値を入れないといけないので少し面倒

`get_all_values`で保存してあるデータを読み込み、今Kaggle APIで入手した最新の投稿と保存されている最新のKernelが違う場合、スプレッドシートの更新とLineの通知を入れるようにしました。

# Line Notifyで通知
Line Notifyというサービスを使用すると、プログラムからLineを簡単に送ることができます。

https://notify-bot.line.me/ja/

上のサイトからLineアカウントでログインし、右上のマイページから、アクセストークンを発行することができます。

`トークンを発行する`ボタンを押すと、トークン名(通知の際に表示される名前)と通知を入れたいトークルームを選択することができるので、個人用のトークルームを設定します。チームで参加している場合、チームのトークルームを作成し、通知を入れるといいかもしれません。

![スクリーンショット 2019-04-10 21.17.36.png](https://qiita-image-store.s3.amazonaws.com/0/224333/46c2547a-9f96-3fbd-665b-55c138445994.png)

pythonファイルからは以下のように呼び出します。`YOUR TOKEN`の部分は発行したトークンを入力します。

https://upura.hatenablog.com/entry/kaggle-notification より引用

```:line.py
def line(Me):
    line_notify_token = 'YOUR TOKEN'
    line_notify_api = 'https://notify-api.line.me/api/notify'
    message = '\n' + Me
    payload = {'message': message}
    headers = {'Authorization': 'Bearer ' + line_notify_token}
    line_notify = requests.post(line_notify_api, data=payload, headers=headers)
```
Kaggleの通知をする場合、`line()`の引数に1.で取得したカーネルのタイトルとURLを入力し、通知を行います。

# 運用
常に通知を受け取れるようにするには、上記の処理をpython等で書いて、n分間隔で実行する必要があります。

私は5分間隔でチェックするようにしましたが、コンペ終盤になると1分間隔とかにした方が良いのかもしれません。

常に起動している必要があるので、Raspberry Piのシェル上で実行するようにしました。以下のように実行しました。

```:kaggle_api_notify.sh
#!/bin/bash
while true
do
  python3 kaggle_notify.py
  sleep 300
done
```

この記事の中で一番面倒だった作業がRaspberry Pi上でPythonの環境構築をする部分でした。詳しくはまたの機会に。

# Discussionについて
今のKaggleでは、Discussionの投稿やコメントが削除できるので、一部の人だけが有効な情報を手に入れることがあります。今(2019年4月)の段階では、Kaggle APIからDiscussionを取得できないので、
`https://www.kaggle.com/forums/130117/topics.json?sortBy=recent&group=all&page=1&pageSize=20&category=all`のようなリクエストを飛ばし、json形式で取得するしかありません。しかし、Kaggleの利用規約の4章h,i節あたりがグレーな気がしています。(Kaggle APIを叩いて情報収拾するのもグレーな気がしますが...)

https://www.kaggle.com/terms

![スクリーンショット 2019-04-10 21.33.05.png](https://qiita-image-store.s3.amazonaws.com/0/224333/cd644c89-c3eb-61a5-103b-4e61ddb7f51f.png)

Kaggle APIにDiscussionを取得する機能が追加されるか、上記のようなリクエストを飛ばして良いのがわかったらDiscussionの通知/保存機能も検討したいと思います。

# GitHub
~~いろいろ整理中なのでしばしおまちを...~~
https://github.com/reo11/Kaggle-notify
とりあえず作りました。READMEとこの記事を読めば使えると思います。

使い辛かったり、バグがあったらプルリクお願いします。