【自動化】WEBサイトの変更を検知してLINEに通知する

# はじめに
高校生になりました。
このコロナの影響で学校にいけなくなり、学校側が用意したWEBサイトを定期的に見てそのサイトに書いてある指示に従って自宅で学習するように言われましたが、そのWEBサイトが重く、また毎日更新を確認するのは面倒です。しかしできるだけ情報は早く入手したいです。そこでWEBサイトの更新を検知してLINEに通知するPythonのプログラムを書くことにしました。  

# 全体の流れ
1. Pythonの標準ライブラリであるurllibを使ってWEBサイトのHTMLを取得します。
2. それをBeautifulSoupというライブラリを使って分析し、必要な部分だけ抽出します。
3. （２ループ目以降）今回抽出したものと前回抽出してファイルに保存したものを比較します。
3. 新しく抽出したものをCSV形式でファイルに保存します。
4. ３で比較した結果をLINENotifyで通知します。

これを繰り返します。
実行するたびに前回実行したときに取得したものと比較します。（このプログラム自体は毎回起動しては終了する）
このプログラム自体に定期的に実行する機能はなく、このプログラムをサーバーにおいてサーバー側で設定して定期的に実行させます。

# 全体の流れの解説
urllibで取得するとHTMLが文字列の状態で取得されます。これをBeautifulSoupというライブラリを使うと簡単にタグ毎に処理することができます。  
csvとはカンマで要素と要素を区切ったファイル形式です。Pythonでのリストのような感じです。  
csvにしたのは単純に扱いやすいからです。標準ライブラリで保存や読み取りができます。
LINENotifyはLINENotifyというアカウントをフォローして管理画面に通知してほしいLINEアカウントでログインして通知したいグループやトークを指定すると、アクセストークンというのが発行され、それを使って指定のURLにアクセスするとLINENotifyのアカウントからメッセージが送られてきます。（いわゆるBotとは違うと思います）

LINENotifyは[https://qiita.com/moriita/items/5b199ac6b14ceaa4f7c9](https://qiita.com/moriita/items/5b199ac6b14ceaa4f7c9)を参考にしました。詳しくはこちらを見てください。


# コード
LINENotifyを参考にした記事ではクラスで分割していたので同じように２つのファイルに分割してみました。

```python:notify.py
import requests
class LINENotifyBot:
    API_URL = "https://notify-api.line.me/api/notify"
    def __init__(self, access_token):
        self.headers = {'Authorization': 'Bearer ' + access_token}

    def send(self, message, image=None, sticker_package_id=None, sticker_id=None):
        message = '\n' + message
        payload = {
                'message': message,
                'stickerPackageId': sticker_package_id,
                'stickerId': sticker_id
            }
        files = {}
        if image != None:
            files = {'imageFile': open(image, 'rb')}
        r = requests.post(self.API_URL, headers=self.headers, data=payload, files=files)

```

```python:main.py
import sys
import csv
import urllib.request, urllib.error
from bs4 import BeautifulSoup
from notify import LINENotifyBot
import logging

# ログの記録するフォーマットを決める
log_format = '%(levelname)s : %(asctime)s : %(message)s'
logging.basicConfig(filename='パス/log.log', level=logging.INFO, format=log_format)
logging.info('START')
url = "変更を検知したいサイトのURL"
bot = LINENotifyBot('アクセストークン')
try:
    # HTMLを取得する
    html = urllib.request.urlopen(url)
    # HTMLのステータスコード（正常に取得できたかどうか）を記録する
    logging.info('HTTP STATUS CODE: ' + str(html.getcode()))
except:
    # 取得に失敗した場合もLINEに通知してログを取る
    bot.send('URLの取得に失敗しました')
    # 念の為強制終了
    sys.exit(1)
soup = BeautifulSoup(html, "html.parser")
# HTMLの中からaタグのみを抽出
tags = soup.find_all("a")
links = list()
# 前回取ったリンク
oldlinks = set()
# 今回とったリンク
newlinks = set()
for tag in tags:
    # aタグからリンクのURLのみを取り出す。
    links.append(tag.get('href'))
try:
    # 前回取得したリンクをファイルから読み込む
    with open('パス/links.csv', 'r') as f:
        reader = csv.reader(f)
        for row in reader:
            oldlinks = set(row)
        logging.info('Opened csv file"')
except:
    # 何かしら失敗した場合はLINEに通知、ログ
    bot.send('ファイルの取得に失敗しました')
    logging.error('Failed to get csv file')

try:
    # 今回取得したリンクを記録する（上書き）
    with open('パス/links.csv', 'w') as f:
        writer = csv.writer(f, lineterminator='\n')
        writer.writerow(links)
    logging.info('Writed csv file')
except:
    # 失敗したら通知、ログ
    bot.send('ファイルの書き込みに失敗しました')
    logging.error('Failed to write csv file')
    sys.exit(1)

try:
    newlinks = set(links)
    # setで引き算をすると差分がわかる
    # 今回新しく発見したリンク
    added = newlinks - oldlinks
    # 前回あったけど今回はなくなったリンク
    removed = oldlinks - newlinks
    for link in added:
        # 追加されたら通知
        # 追加されたURL自体もお知らせしようとしたらリンクをむやみに貼るなと書いてあったので一応やめておいた
        bot.send('リンクが追加されました')

    for link in removed:
        # 追加と同様に
        bot.send('リンクが消去されました')

    logging.info('Compared links')

except:
    # 失敗したら…（以下略）
    bot.send('比較に失敗しました')
    logging.error('Failed to compare')
    sys.exit(1)

logging.info('DONE')
```
`パス`というのはこれらのプログラムやログ、記録しているファイルを保存している場所です。
同じフォルダにすべてが入っていればおそらく`パス/`はいらないと思います。

`変更を検知したいサイトのURL`はそのまま、定期的に見たいサイトのURLです。

`アクセストークン`はLINENotifyのアクセストークンです。詳しくは[https://qiita.com/moriita/items/5b199ac6b14ceaa4f7c9](https://qiita.com/moriita/items/5b199ac6b14ceaa4f7c9)を見てください。

loggingライブラリを使ってログも取っています。
これはたまにWEBサイトがうまく取得できているか確認するためです。
loggingに関しては[https://qiita.com/__init__/items/91e5841ed53d55a7895e](https://qiita.com/__init__/items/91e5841ed53d55a7895e)こちらのサイトを参考に作ったので詳しくはこちらを見てください。

#　実行の仕方
VPSを借りてこれらのファイルを置いてcronで定期的に実行しています。
[https://qiita.com/msrks/items/6a180e03d7af622f2101](https://qiita.com/msrks/items/6a180e03d7af622f2101)にかいてあることをそのまま今回のPythonファイルに置き換えるだけです。
おそらくAWSのLamdaでもできると思います([https://qiita.com/Toshinori_Hayashi/items/5b0a72dc64ced91717c0](https://qiita.com/Toshinori_Hayashi/items/5b0a72dc64ced91717c0))使ったことないのでわからないです。
ラズパイが一番楽かもしれないです。

# 最後に
読んでくださりありがとうございました。
なにか間違いや質問、改善点などをみつけましたらコメントや編集リクエスト等をお願いします。