スクレイピングでwebサイトが更新されたらSlackで通知 [Python]

## 💡はじめに
所属してるサッカーチームで公式試合のスケジュールがあるサイトで更新されるのですが、最新のスケジュールを把握するため毎日サイトを見に行く必要があり、めんどくさいなあと思ってました。
ちょうどPythonを勉強していたこともあって、「これスクレイピングで解決できるやん？」ということでそのプログラムを勉強がてら作ってみました。


## 📄書いてること
ブログやお知らせ一覧などの定期的に更新されるページから、更新されたらSlackで更新内容を通知するプログラム
詳しいスクレイピングやcronのついては記載していないので参考記事を見ていただければと思います。
#### 大まかな流れ
- 対象サイトをスクレイピングして必要情報取得
- 更新がないかをチェック
- 更新があれば内容をSlackで通知

この処理をcronで定期実行することでわざわざそのサイトを見に行かなくても更新があったと気づくことができます。
ただmac上で実行しているのでmac起動しているときしか動きません。

## ⚙️環境
macOS : Big Sur
conda version : 4.9.2
Python : 3.8.5


## 💻スクレイピング
- **ライブラリインポート**

```python
import csv
import os
import sys
import requests

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

- **対象サイトのほしい情報ををスクレイピングで取得**

```python
def scraping():
    url = '対象ページURL'
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'html.parser')
    result = []
    for top_news in soup.find_all(class_='取得したい箇所のクラス名'):
        result.append([
            top_news.text,
            top_news.get('href')
        ])
    return result

```
`soup.find_all(class_='取得したい箇所のクラス名')`の中身はyahooニュースでやるとこんな感じでリストに入って取得できます
![スクリーンショット 2021-03-18 12.57.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464231/2e117d3b-7aac-b3bf-ecb1-d19e65814dec.png)
今回だと、これでタイトルとリンクを取得しリストに格納

```python
result.append([
    top_news.text, #タイトル
    top_news.get('href') #リンク
])
```

- **取得した情報をcsvに吐き出す**

リストに格納されたタイトルとリンクを一行ずつ取り出してcsvに保存

```python
def output_csv(result):
    with open('last_log.csv', 'w', newline='',encoding='utf_8') as file:
        headers = ['Title', 'URL']
        writer = csv.writer(file)
        writer.writerow(headers)
        for row in result:
            writer.writerow(row)
```
こんな感じ
![スクリーンショット 2021-03-18 13.56.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464231/e50b6522-7ef5-c223-95f6-1f85992b2fc8.png)

- **csvを読み込む**

ファイルの存在確認と中身の確認を行う

```python
def read_csv():
    if not os.path.exists('last_log.csv'):
        raise Exception('ファイルがありません。')
    if os.path.getsize('last_log.csv') == 0:
        raise Exception('ファイルの中身が空です。')
    csv_list = pd.read_csv('last_log.csv', header=None).values.tolist()
    return csv_list
```
`tolist()`とすることでDataFrame型のものをリストにして返します
これが、![スクリーンショット 2021-03-18 16.39.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464231/7c251b47-ea6c-b730-6c79-b31935093d3e.png)
こう！
![スクリーンショット 2021-03-18 16.39.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464231/d1aa964e-f8e2-b75d-a7d3-4a6ee956964c.png)


- **1つ前に保存したcsvと中身を比較して中身が変わったものだけ取り出す**

直近で取得したリストをforで回し、その中で前回取得したリストの中に含まれていないものを新しいリストに入れます

```python
def list_diff(result, last_result):
    return_list = []
    for tmp in result:
        if tmp not in last_result:
            return_list.append(tmp)
    return return_list
```

ここまでで更新された内容が取得できました🙌

## 🔔Slackに通知（Incoming WebHooks）

Webhook URLについては[この記事](https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008)が参考になりました

- **チャンネル選択**
通知を送るチャンネルを設定します
![スクリーンショット 2021-03-18 15.49.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464231/b193f985-cc08-20b1-e301-18fb73ff5682.png)
`Add Incoming Webhooksintegration`をクリック

- **Webhook URLをコピー**
![スクリーンショット 2021-03-18 15.52.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464231/6e094539-221d-dc16-20fe-054028c5cec9.png)

- **コピーしたWebhook URLを設定**

```python
import slackweb

slack  = slackweb.Slack(url='コピーしたWebhook URL')
slack.notify(text='こんにちは、世界')
```

こんな感じ
![スクリーンショット 2021-03-18 19.31.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464231/524c6c71-c1a0-b4c2-32a4-346b63671587.png)


- **更新されたものだけ通知**

list_diff関数で取り出した更新されたものをSlackで通知する

```python
def send_to_slack(diff_list):
    text = '<!channel>\n' #@channelメンション
    for tmp in diff_list:
        text += tmp[0] + '\n' + tmp[1] + '\n'
    slack  = slackweb.Slack(url='コピーしたWebhook URL')
    slack.notify(text=text)
```
こんな感じ
![スクリーンショット 2021-03-18 19.33.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464231/0efa733e-f505-74bf-8611-49f7f5116f51.png)

## 🔄cronで定期実行

cronの使い方については[この記事](https://tanuhack.com/cron/)を参考にしました

- **cronにファイルのアクセス権限を与える**

cronからPCにあるファイルのアクセス権を設定します
これを設定しないとcronが言うことを聞いてくれません
[この記事](https://admin-it.xyz/operation-efficiency/mac-cron-does-not-work-well/)が参考になりました

- **PATHを通す**

今回実行した環境がanacondaなので`echo $PATH`とターミナルに打つことによってPythonのPATHがわかります
そのPATHをcrontabを開いてPATHに代入します
こんな感じ（人によって変わると思います）

```terminal
PATH=/Users/username/anaconda3/bin:~
```

- **ログを吐かせる**

```terminal
* */1 * * * python /Users/hogehoge/main.py >> /Users/hogehoge/cron.log 2>&1
```

`>>`を指定することでpyファイルの出力結果（print関数で表示させてもの）を特定のログファイルに追記をすることができます
`2>&1`を指定することでpyファイルの出力結果に加えて、コマンドの実行時に起こったエラーもcron.logというファイルに記録されます

エラーの場合はこんな感じ

```
Traceback (most recent call last):
  File "/Users/hogehoge/main.py", line 78, in <module>
    main()
  File "/Users/hogehoge/main.py", line 66, in main
    last_result = read_csv()
  File "/Users/hogehoge/main.py", line 41, in read_csv
    raise Exception('ファイルがありません。')
Exception: ファイルがありません。
```
時間なども一緒に出力させるとよりいいですね

標準出力など詳しくは[この記事](https://qiita.com/laikuaut/items/e1cc312ffc7ec2c872fc)が参考になるかと思います

## 🤦‍♂️つまづきポイント
**・csvファイルの比較ができない**
read_csv関数で読み込んだcsvをDataFrame型で返していたたため、最新情報を取り込んだリストと比較ができていなかった
→pandasで読み込んだcsvはリストに型変換してから比較を行うことで解決🙆‍♂️

**・cronうまく動かない**
cronのファイルアクセス権がなかった
→システム環境設定からアクセス権付与で解決🙆‍♂️
mac標準のPythonを指定していた
→anacondaインストールしたときに追加されたPATHを指定することで解決🙆‍♂️

## 📦まとめ
いちいちサイトを見に行く手間が省けました🙌