【Python】BeautifulSoupで複数サイトをスクレイピングして更新通知をChatWorkで送信する


PythonのBeautifulSoupを使って、
複数のサイト（の最新記事）をスクレイピングし、
更新があればChatWorkに通知を送信するシステムを作成しました。

備忘録のために書き留めておきます。

<h2>メインとなるスクレイピングのコード</h2>

まずは必要なライブラリやモジュールをインポートします。

config や chatwork への通知処理は後ほど別ファイルに書きます。

```python:site_checker.py
# coding: UTF-8
import requests
from bs4 import BeautifulSoup
from my_config import my_config, update_recent_article
from chatwork_notification import chatwork_notify
```

&nbsp;
サイト別に記事一覧を取得する関数を書きます。
スクレイピングしたいサイトごとに、最新記事のDOMを取得するように調整してください。

```python:site_checker.py
def extract_articles(page_html, type):
    if type == "A":
        main = page_html.find("div", class_="inner-news")
        articles = main.find_all("li", class_="table-view-cell media")
    elif type == "B":
        articles = page_html.find("div", class_="comicIndex-rightBox")
    return articles
```

&nbsp;
次に、取得した記事から、記事のURLを抽出する関数を書きます。

```python:site_checker.py
def extract_article_url(article, type):
    domain = ""
    if type == "A":
        domain = "https://AAA.jp"
    elif type == "B":
        domain = "https://BBB.com"

    url = domain + article.get("href")
    return url
```

&nbsp;
次に、取得した記事から、記事のタイトルを抽出する関数を書きます。

```python:site_checker.py
def extract_article_title(page_html, article, type):
    page_title = ""
    if type == "A":
        title = html.find("div", class_="media-body").getText()
        title = title.replace("\n"," ")
    elif type == "B":
        title = html.find("span", class_="titleBox").getText()
    return title + " " + page_title
```

&nbsp;
取得した記事から、更新された記事を抽出します。
更新前の最新の記事のindexはmy_config.iniで管理します。

```python:site_checker.py
def extract_update_article(page_url, articles, recent_article, name, type):
    article_list = []
    first_article_num = ""
    # 逆順に取得していき、最新記事より１つ古い記事のurlを取得
    for article in articles:
        if article == 0:
            continue
        url = extract_article_url(article, type)
        # URLから記事番号を取得
        if type == "A":
            article_num = url.replace(page_url + "episode/", "").replace("/", "")
        elif type == "B":
            article_num = url.replace("https://BBB.com/viewer/?cid=", "")

        if recent_article == article_num:
            break

        title = extract_article_title(page_html, article, type)
        article_list.append([title, url])

        if first_article_num == "":
            first_article_num = article_num

    # my_configを更新
    if first_article_num != "":
        update_recent_article(name, first_article_num)

    return article_list
```

&nbsp;
メインの処理を書きます。

```python:site_checker.py
if __name__ == "__main__":
    # スクレイピングしたいサイト数だけ処理を繰り返す
    for i in range(1, 2):
        # my_configを取得
        name = "web_info_" + str(i)
        page_url = my_config[name]['url']
        page_type = my_config[name]['type']
        recent_article = my_config[name]['recent_article']

        # スクレイピングする
        res = requests.get(page_url)
        page_html = BeautifulSoup(res.text, 'html.parser')

        # 記事一覧を取得する
        articles = extract_articles(page_html, page_type)

        # 更新された記事を取得する
        article_list = extract_update_article(page_url, articles, recent_article, name, page_type)

        # 更新された記事があれば通知する
        if len(article_list) > 0:
            chatwork_notify(list_=article_list)

```

<h2>設定ファイルに書くコード</h2>

my_config.py に、
my_config.ini ファイルの読み取りと、
my_config.ini ファイルへの書き込み処理を書きます。

```python:my_config.py
# coding: UTF-8
import configparser
import re

my_config = configparser.ConfigParser()
my_config.read('./my_config.ini')

# recent_articleの記事番号を更新する関数
def update_recent_article(name, article_num):
    with open('./my_config.ini', 'r') as f:
        lines = f.readlines()

    with open('./my_config.ini', 'w') as f:
        name_flag = False
        for line in lines:
            if re.match(r'(\[' + name + '\])', line):
                name_flag = True
            if re.match(r'(recent_article =)', line):
                if name_flag:
                    name_flag = False
                    f.write("recent_article = {}\n".format(article_num))
                    continue
            f.write(line)
```

次にスクレイピングサイトの情報を.iniファイルに書きます。

```ini:my_config.ini
# coding: UTF-8
[chatwork_info]
in_webhook_url = 1

# サイトA
[web_info_1]
type = A
url = https://AAA.jp/
recent_article = 1

# サイトB
[web_info_2]
type = B
url = https://BBB.jp/
recent_article = 1
```

スクレイピングするサイトを増やしたい場合は、
上記と同じ形式で増やしてください。

その際、web_info_◯ の番号が重複したりずれたりしないように注意してください。


<h2>チャットワークへの通知コード</h2>

```python:chatwork_notification.py
# coding: UTF-8
import requests

APIKEY = '取得したAPIキーを入力'
ENDPOINT = 'https://api.chatwork.com/v2'
ROOMID = '通知したいルームのIDを入力'

post_message_url = '{}/rooms/{}/messages'.format(ENDPOINT, ROOMID)

def chatwork_notify(list_=None):
    headers = {'X-ChatWorkToken': APIKEY}

    text = ""
    for element in list_:
        if text != "":
            text += "\n"
        text += element[0] + "\n" + element[1]

    params = {'body': text, "self_unread": 1}
    requests.post(post_message_url, headers=headers, params=params)
```

APIKEYとROOMIDはチャットワークにログインして取得します。

chatwork_notify関数は、site_checker.py で使用するものです。
通知したい内容に応じて、文章（text）等を好きに変更してください。