初心者がPythonでウェブスクレイピング(3) crontab

今回は、[前回](https://qiita.com/inoken0926/items/65be0fabee0752d02658)で「Yahoo！ニューススクレイピングをgoogleスプレッドシートに連携」をローカルで自動実行してみたいと思います。
自動実行にはcrontabを使います。
crontabを使えば、定期的にコマンドが実行できます。分、時、日、月、曜日で細かくスケジュールが設定できます。

#crontabの使い方
##主なオプション

| オプション | 説明 |
|:-:|---|
| -e  |  編集。エディターが立ち上がってcrontabを編集できます。 |
| -l  |  現在のcrontabを標準出力に表示します。 |
| -r  |  現在のcrontabを削除します。 |

##crontabのフィールド
crontabのフィールドは、分、時、日、月、曜日の5種類があり、最後に起動したい処理を記述します。

```bash:
# cronの起動時間設定
 * * * * * （起動したい処理）
#| | | | |
#| | | | |- 曜日 day of week 0-7 (0 or 7 is Sunday, or use names example:"mon,wed,fri)
#| | | |--- 月 month         1-12 (or names example:"jan-mar")
#| | |----- 日 day of month  1-31
#| |------- 時 hour          0-23
#|--------- 分 minute        0-59
```
##crontabの設定サンプル
以下、crontabの設定サンプルです。

```bash:crontab
* * * * * （起動したい処理）      # 毎分起動する
*/3 * * * * （起動したい処理）    # 3分ごとに起動する
20 * * * * （起動したい処理）     # 毎時20分
10,30 * * * * （起動したい処理）  # 毎時10分と30分（カンマ指定）
0-10 * * * * （起動したい処理）   # 毎時00〜10分に、1分毎に起動（ハイフン指定）

1 3 * * * （起動したい処理）    # 毎日3時01分
0 1,3 * * * （起動したい処理）  # 毎日1時と3時に、00分に起動（カンマ指定）
0 1-6 * * * （起動したい処理）  # 毎日1時〜6時まで、00分に起動（ハイフン指定）
0 */2 * * * （起動したい処理）  # 2時間ごとに起動する（0時、2時、4時、...）

# 0:sun 1:mon 2:tue 3:wed 4:thu 5:fri 6:sat 7:sun
0 9 * * 0 （起動したい処理）    # 毎週日曜日の午前9:00に起動
0 9 * * 1,3 （起動したい処理）  # 毎週月,水曜日の午前9:00に起動（カンマ指定）
0 9 * * 1-5 （起動したい処理）  # 毎週月〜金曜日の午前9:00に起動（ハイフン指定）

10 4 1 * * （起動したい処理）  # 毎月1日の4:10に起動
10 4 3,13 * * （起動したい処理）  # 毎月3日と13日の、4:10に起動（カンマ指定）
10 4 3-5 * * （起動したい処理）  # 毎月3〜5日の4:10に起動（ハイフン指定）

0 10 3 1 * （起動したい処理）  # 毎年1月3日の10:00に起動
```





##（起動したい処理）の設定
次に、（起動したい処理）の設定の仕方について見ていきます。

まず、cronが動いた時間を記録するPGMを作ってテストします。

```python:cron-test.py
from datetime import datetime

datetimestr = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
print(datetimestr,'cronが動いた！')
```

このPGMを動かすために、通常、普通にpyがあるディレクトリに移動して、pythonを実行する時は、以下のような感じですね。

```bash:bash
% python cron-test.py
```
これをこのまま（起動したい処理）に記載してみます。
加えて、logはリダイレクトします。(logはユーザーのホームディレクトりに作られます。)

```bash:crontab
* * * * * python cron-test.py >> cron-test.log 2>&1
```
```bash:cron-test.log
/bin/sh: Applications: command not found
```
動きません。留意すべき落とし穴が３つあります。
①当該のディレクトリにあらかじめ移動しないと、うまく動かない。
②パスはフルパスでないと認識しない。
③cron独自にもつPATH環境変数がデフォルトだと、うまく動かない。

###①当該のディレクトリにあらかじめ移動しないと、うまく動かない。
そもそもホームディレクトリで動こうとするみたいなので、正しいディレクトリに移動してから、動かしてみます。
通常コマンドと同様に、cdで実行ディレクトリに移動。；で区切ってpythonコマンドを指定します。

```bash:crontab
* * * * * cd /Users/inoken/git-repository/env2; python cron-test.py >> cron-test.log 2>&1
```

```a:bash
python: can't open file 'cron-test.py': [Errno 2] No such file or directory
```
エラーは変わりますが、動きません。

###②パスはフルパスでないと認識しない。
パスはとにかくフルパスでないと受け付けないようです。
とりあえず、pythonとpyファイルのフルパスを確認します。

```bash:bash
% which python   
/Users/inoken/git-repository/env2/env2/bin/python
% pwd
/Users/inoken/git-repository/env2
% ls -la | grep cron
-rw-r--r--   1 inoken  staff     131  9 19 17:46 cron-test.py
```

ディレクトリ移動した上で、さらにフルパス指定します。

```a:crontab
* * * * * cd /Users/inoken/git-repository/env2; /Users/inoken/git-repository/env2/bin/python /Users/inoken/git-repository/env2/cron-test.py >> cron-test.log 2>&1
```

```bash:bash
/bin/sh: Applications: command not found
```
まだダメです。

ちなみにディレクトリ移動せず、pythonとpyファイルをフルパスにしても、同様に動きません。

```a:crontab
* * * * * /Users/inoken/git-repository/env2/bin/python /Users/inoken/git-repository/env2/cron-test.py >> cron-test.log 2>&1
```

```bash:bash
/bin/sh: Applications: command not found
```

###③cron独自にもつPATH環境変数がデフォルトだと、うまく動かない。
cronのenvを出力してみます。

```a:crontab
* * * * * cd /Users/inoken/git-repository/env2; env > cron.log 2>&1
```
cdしてもPWDは変わってないのは不思議ですが、PATHが/usr/bin:/binにしか通っていません。

```a:env
% cat ~/cron.log
SHELL=/bin/sh
USER=inoken
PATH=/usr/bin:/bin
PWD=/Users/inoken
SHLVL=1
HOME=/Users/inoken
LOGNAME=inoken
_=/usr/bin/env
```
ですので、PATHに少なくともpythonのパスを通します。
venv環境の環境変数を確認して、丸ごとパクります。

```bash:bash
% echo $PATH
/Users/inoken/git-repository/env2/env2/bin:/Users/inoken/google-cloud-sdk/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/inoken/google-cloud-sdk/bin
```
そして、先頭にPATHを指定した後、サイクルとコマンドを指定します。

```bash:crontab
PATH=/Users/inoken/git-repository/env2/env2/bin:/Users/inoken/google-cloud-sdk/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/inoken/google-cloud-sdk/bin

* * * * * cd /Users/inoken/git-repository/env2; /Users/inoken/git-repository/env2/bin/python /Users/inoken/git-repository/env2/cron-test.py >> cron-test.log 2>&1
```
長くて死にそうですが、仕方ありません。
PATHが塗り変わってくれて、ようやく無事動きました。

```a:env
SHELL=/bin/sh
USER=inoken
PATH=/Users/inoken/git-repository/env2/env2/bin:/Users/inoken/google-cloud-sdk/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/inoken/google-cloud-sdk/bin
PWD=/Users/inoken
SHLVL=1
HOME=/Users/inoken
LOGNAME=inoken
_=/usr/bin/env
```
```bash:bash
% cat ~/cron-test.log
2020/09/19 21:49:01 cronが動いた！
2020/09/19 21:50:00 cronが動いた！
2020/09/19 21:51:00 cronが動いた！
```

#crontabでスプレイピングを自動実行
いよいよ本題。
スクレイピングのPGMを動かします。
と言っても、先ほどのテストpyファイル名を塗り替えるだけです。（log名は適宜。）

```bash:crontab
* * * * * cd /Users/inoken/git-repository/env2; /Users/inoken/git-repository/env2/bin/python /Users/inoken/git-repository/env2/requests-test2.py >> /Users/inoken/log/cron3h.log 2>&1
```

```a:cron3h.log
2020/09/19 17:37:51 スクレイピングを終了しました。
```
![スクリーンショット 2020-09-20 9.05.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705126/b1fa40b5-4eb3-757f-8087-5f278b421f30.png)

無事、自動実行に成功しました。

##自動実行の課題
自動実行は成功しましたが、さらに難題があります。
###④MacBookスリープ中は動かない。
ずっとスリープさせないのも無駄ですし、
他に何か方法はあるのかもしれませんが、難易度は高いでしょう。

1分おきや3分おきはテストは以下のようにできたとしても、それ以上大きいサイクルなどはスリープしてしまうと動かすことができません。

1分おき

```bash:bash
% cat /Users/inoken/log/cron-test.log
2020/09/19 17:50:01 cronが動いた！
2020/09/19 17:51:00 cronが動いた！
2020/09/19 17:52:00 cronが動いた！
2020/09/19 17:53:00 cronが動いた！
```

3分おき

```bash:
% cat /Users/inoken/log/cron-test.log
2020/09/19 19:12:01 cronが動いた！
2020/09/19 19:15:00 cronが動いた！
2020/09/19 19:18:00 cronが動いた！
```

##１日１回なら定期的にやる方法あり
ただ、１日１回なら定期的にやる方法はあります。
Macのシステム環境設定 > 省エネルギー > スケジュール で、
毎日のスリープ解除の時間を設定することができます。
![スクリーンショット 2020-09-20 21.11.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705126/4b87e1ea-bf9c-4299-e7d4-d153ed067c64.png)

このスリープ解除されている時間帯にすかさずcrontabの時間を合わせて自動実行を行えば、一応実行は可能です。（スリープの設定によっては、またすぐスリープするので、夜中3:00にスリープ解除して、3:01に自動実行する、などすかさず自動実行すると良いです。）

```bash:crontab
1 3 * * * cd /Users/inoken/git-repository/env2; /Users/inoken/git-repository/env2/bin/python /Users/inoken/git-repository/env2/requests-test2.py >> /Users/inoken/log/cron3h.log 2>&1
```

```bash:bash
% cat /Users/inoken/log/cron3h.log
2020/09/20 03:01:29 スクレイピングを終了しました。
```

![スクリーンショット 2020-09-20 21.22.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705126/564bc295-465e-a059-2d39-cd1d7fe261dc.png)
無事に夜中の3:01に自動実行されました。

#まとめ

Macでのcrontab実行にあたっての注意点
**①当該のディレクトリにあらかじめ移動しないと、うまく動かない。**
**②パスはフルパスでないと認識しない。**
**③cron独自にもつPATH環境変数がデフォルトだと、うまく動かない。**
**④MacBookさんスリープ中は動かない。**

#参考サイト
https://man7.org/linux/man-pages/man5/crontab.5.html