Yahoo NewsをSlackへ通知してみる ~AWS Lambda × Web scraping × Slack API~

#はじめに
今回はAWS Lambdaを使ってSlackに通知を送るというシンプルなサーバレスアーキテクチャ作成を紹介します。
すでにLambdaとSlackについては多くの記事が出ていますがWeb scrapingを組み合わせたものはあまりないように思いました。
私がクラウド技術のすごさを思い知ったのがこの**Lambda×Web scraping**という組み合わせだったので初心に帰るという意味で記事にします！

#Web Scrapingとの出会いと課題
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/1d174d88-1680-627f-010a-cdd57bb852c0.png)
>資料出所：[Webスクレイピングとは？定義から活用事例までの説明](https://www.octoparse.jp/blog/web-scraping/ "Webスクレイピングとは？定義から活用事例までの説明")

##1.Web Scraping
皆さんはWeb scrapingという技術をご存じでしょうか？
Web scrapingとは**プログラムを使ってWebサイトから文字列や画像を自動で取得する技術**のことです。
例えばデータ分析のためにAmazonなどの通販サイトから商品名と価格を1日1000件、1カ月間取得する必要があるとします。これを人間の手でやると何人もの社員を動員してひたすら作業を繰り返す必要がありますしミスが生じる可能性も高いです。
しかしWeb scrapingを使って行えば自動で数分で完了するので毎日実行しても負荷はほとんどなくミスも起こりません。**お金・時間・労力を大幅に削減できるだけでなく品質を向上することができます**。
私が初めてIT技術に触れ、感動してIT業界を志すきっかけになったのがWeb scrapingです。大学の卒業論文のためにスポーツの対戦データをWeb scrapingによって1時間で5万件取得し大規模データでの分析が短時間で可能になりました。

##2.課題
Web Scrapingを一度使うだけであればPythonでプログラムを実装して実行するだけでよいですが、定期的に実行する場合はPCかサーバを用意して常時稼働状態にしてプログラムを定期実行するロジックを構築する必要があります。
以前株価や経済指標を自動取得してSlackへ送信するPythonプログラムを実装した際にはRaspberry Pi（[Raspberry Pi（ラズベリーパイ）とは？IoT開発ができるラズパイの使い方](https://www.internetacademy.jp/it/programming/programming-basic/how-to-use-raspberry-pi.html "Raspberry Pi（ラズベリーパイ）とは？IoT開発ができるラズパイの使い方")）を購入して24時間起動状態にしてプログラムを定期実行していました。
しかしこの方法だと冷却用のファンが夜うるさかったり盗難・紛失などのセキュリティリスクがあったりしたのでどうにか改善が必要でした。

<code>Ryoが構築した定期実行システムver 1.0</code>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/7e7b854b-75da-a5a5-f74b-586dd20ab740.png)

#AWS Lambdaという解決策
そこで着目したのが**AWSが誇るサーバレスサービス「AWS Lambda」**です。
サーバレスサービスとは簡単に言うと**「サーバのことを気にしなくてよくなるクラウドサービス」**のことです。
私が初期に構築したRaspberry Piを用いたアーキテクチャの場合、私はRaspberry Piというサーバの管理を常に行わなくてはいけません。つまり盗難防止、冷却装置、故障対策などに加えてOSの更新やセキュリティ適用などのソフト面も管理する必要があります（いわゆるサーバ運用保守作業）。
しかしLambdaを利用すれば実行するPythonプログラムを配置するだけで**残りのサーバの世話はすべてAWSが責任を持って行ってくれます**。時間ごとの定期実行に加えてシステムの障害発生やアクセス増加を起点（トリガー）として全自動でプログラムを実行してくれます。サーバ費用は不要で**プログラムを実行した回数に応じて課金**されるので変動費のみとなりやすく済みます（実際はLambdaの無料枠があるので大量実行しない限り無料）。

<code>Ryoが構築した定期実行システムver 2.0</code>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/f9a46556-d31c-03f0-9ee9-c0887868e113.png)


#やりたいこと
<code>実現したい定期実行システム</code>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/ab86ec7b-151b-df45-65a0-d90347a31480.png)

今回は前述のLambdaによる自動定期実行とWeb scrapingを組み合わせて**1日1回YahooニュースのサイトからトップニュースのタイトルとURLを自動取得してSlackに通知する**というシステムを作ります。
完成するとすべてが自動化されて達成感を得られる＆WebScrapingとクラウドサービスのすごさがわかるのでぜひ挑戦してみてください。

:::note alert
Web scrapingは短時間にたくさん実行してしまうと先方のサーバに負荷をかけてしまい場合によっては攻撃とみなされて偽計業務妨害となってしまう可能性があります。
普通に実行していて犯罪行為となる可能性は低いですがマナーを守って高頻度の実行は控えるように、そして自己責任でお願いいたします。
:::
>参考：[岡崎市立中央図書館事件](https://ja.wikipedia.org/wiki/%E5%B2%A1%E5%B4%8E%E5%B8%82%E7%AB%8B%E4%B8%AD%E5%A4%AE%E5%9B%B3%E6%9B%B8%E9%A4%A8%E4%BA%8B%E4%BB%B6 "岡崎市立中央図書館事件")

#手順
**事前準備**

+ **Slackにアカウント登録が完了していること**
+ **Slack管理者として管理画面が利用できること**
+ **AWSのアカウント登録が完了しクレジットカード登録も完了していること**
+ **Python実行環境(Anaconda推奨)が構築されていること　参考：[Anacondaのインストール](https://www.javadrive.jp/python/install/index5.html "Anacondaのインストール")**

これらの一般的な事前準備に関してはQiitaの記事やネットの情報を参考に済ませておいてください。
さほど難しくはないと思います。

##1.Slackの設定とアプリ登録

[Slackのアプリ管理画面](https://api.slack.com/apps "Slackのアプリ管理画面")を開き「Create an App」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/1d8ce6b9-07a0-45eb-8f8d-3549d14b6c32.png)

---
「From scratch」をクリックし、アプリ名に「daily_news」（なんでもよい）と入力してアプリを導入するworkspaceに間違いがないことを確認して「Create App」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/2dd400b2-eb4f-d01f-efab-63d2d3caf560.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/6744f694-343f-0a6e-f306-339022f22368.png)
完了するとアプリの詳細画面が表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/c05e3e92-54a4-a4c6-15b7-8a3983339711.png)

---
「Incoming Webhooks」をクリックしタブをONにします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/6d881445-b51a-b8e2-38ae-90d3adead014.png)

---
画面を下までスクロールし「Add New ... Workspace」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/8479e312-fc95-6c1f-7de5-e14d4dc2bdb6.png)

---
権限がリクエストされるので追加する（メッセージを飛ばす先の）チャンネルを選択して「許可する」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/fa284b27-e46e-3e50-3668-f58ed5ddd1db.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/b19195b1-cbd3-830b-1b95-a3d9a0fb89b6.png)

---
完了するとこのようになります
ここで発行されたWebhook URLは後程必要になりますので覚えておいてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/670df394-784c-35ad-5471-6249ebf8dd43.png)

##2.Pythonスクリプト作成
以下4つのPythonファイル（.py）を準備します

①BeautifulSoupライブラリでYahooニュースページからニュース情報を取得するコード

```python:get_yahoo_news.py
import requests
from bs4 import BeautifulSoup

def get_title_url(url, tag, tag_string):
    
    # スクレイピング対象の URL にリクエストを送り HTML を取得する
    res = requests.get(url)
    
    # レスポンスの HTML から BeautifulSoup オブジェクトを作る
    soup = BeautifulSoup(res.text, 'html.parser')
    
    # ページに含まれるタイトル、リンクを全て取得する
    titles = []
    for i in soup.select("[{0}='{1}']".format(tag, tag_string)):
        titles.append(i.get_text())

    urls = []
    for i in soup.select("[{0}='{1}']".format(tag, tag_string)):
        urls.append(i.get("href"))

    return titles, urls


def make_news_messgae(url, tag, tag_string):

    news_titles, news_urls = get_title_url(url, tag, tag_string)
    fixed_message = ""

    for i in range(0, len(news_titles)):
        message = []
        for j in range(0, len(news_titles)):
            message.append("*・<{}|{}>*".format(news_urls[j], news_titles[j]))
        fixed_message = fixed_message + message[i] + "\n"
    
    return fixed_message
```
---
②SlackAPIを呼び出しPOSTリクエストを送るコード

:::note warn
"sc-frDJqD bJCuGd"の部分はYahooニュースページのタグから取得していますが変更になる可能性がありますので取得に失敗する場合はYahooニュースページでF12ボタンを押してクラス名を確認して変更してください
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/71c964bd-b447-aff4-1e8c-f50f5eda4efd.png)


```python:send_request.py
import requests, json
import datetime
from get_yahoo_news import make_news_messgae

def dt_today():
    dt = datetime.datetime.today() + datetime.timedelta(hours=9)
    dt_today = dt.strftime("%m/%d")
    return dt_today

def send_news(url):
    #日付取得
    today = dt_today()

    #ニュースタイトル、URL取得
    news_message = make_news_messgae('https://news.yahoo.co.jp/', "class", "sc-frDJqD bJCuGd")

    #送信先SlackチャンネルのWebhookURLを指定
    WEB_HOOK_URL = url

    #POSTリクエスト送信
    requests.post(WEB_HOOK_URL, data = json.dumps({
        'text': '<!here>\n{0}の注目ニュースはこちらです！\n\n{1}'.format(today, news_message),
    }))
    return("finish_work_alert_{0}".format(today))
```
---
③SlackのWebhook URLのリスト（今回はnews_channelのURLのみ使用しますがテストのために他のチャンネルのURLを発行して記入してもよいです）

:::note warn
「Webhook URLをコピー」の部分は https://hooks.slack.com/services/XXXXXXX の形式で記入してください
:::


```python:slack_url_list.py
def slack_url_list():
    slack_url_list={
    "news_channel":"Webhook URLをコピー",
    "random":"Webhook URLをコピー" # ←無くてもよい
    }
    return slack_url_list
```
---
④Lambdaを制御するハンドラーファイルで、ここに記述されている関数が実行されます

```python:lambda_function.py
import json
import datetime
import send_request
from slack_url_list import slack_url_list

def lambda_handler(event, context):

    dt = datetime.datetime.today() + datetime.timedelta(hours=9)
    slack_urls = slack_url_list()

    send_request.send_news(slack_urls["news_channel"])
    
    return {
        'statusCode': 200,
        'body': json.dumps('{}_finished'.format(dt.strftime("%Y%m%d_%H%M%S")))
    }
```
---

##3.ライブラリ資源の確保
requests、BeautifulSoupなどのPythonライブラリはローカルPC上では一度インストールすれば自動的に呼び出されますがLambda上では呼び出せないので必要なライブラリはコードファイルと同じディレクトリに保存しておく必要があります。
今回は事前準備で**Anaconda経由でPythonが実行できる環境を整えている**と仮定して進めます（独自にPythonをインストールした方はネットでpip install コマンドが使えるようにしていればOKです）。
まずは必要なライブラリをAnaconda promptからインストールします

---
Anaconda promptの起動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/23102092-fbb9-12d2-78dd-e47a5dc45187.png)

---
必要なライブラリのインストール
以下のような画面で
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/df20a5f4-6466-cc76-51a9-edbaf86f13e4.png)
下記のコマンドを入力してください

```
pip install requests
pip install bs4
pip install soupsieve
```
---
インストールされたライブラリは以下のディレクトリに保存されているので探してフォルダごとコピーしてください

```
C:\Users\"ユーザー名"\Anaconda3\Lib\site-packages
```
---
最後に4つのPythonファイルと3つのライブラリフォルダを1つのフォルダに保存します（場所はどこでも大丈夫です）
<code>完成形<code>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/2640727d-70d1-f236-1bba-b07aeb05f105.png)

##4.AWS Lambdaへのデプロイ

Lambdaへのデプロイのためには資源をすべて選択してZIPファイルとしてまとめておく必要があります
必要なファイル、フォルダを全選択して1つのZIPファイルとして保存してください（ZIPファイル名は「daily_news」としています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/067911ff-11a7-be76-16ee-68522661c3f3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/280dce75-3225-06f0-6084-e76f9658ec3a.png)


---
AWSのコンソール画面から「AWS Lambda」と検索し、「関数の作成」をクリックします
作成画面で関数名「daily_news」を入力、ランタイム「Python 3.7」を選択し他はいじらずに「関数の作成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/947bfff6-e424-c81d-17aa-9798013db125.png)

---
詳細画面が表示されるので「アップロード元」をクリックして「.zipファイル」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/412c214a-fe72-7fe9-9ab4-f0eec6dcbb57.png)

---
先ほど作成したZIPファイルを選択して「保存」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/7b92876d-d7da-4b1c-af8e-f9d6d7e11eeb.png)

---
<code>完成形<code>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/dbfc43f2-59a7-e798-b4dc-a6856bbbc374.png)

##5.テスト
「Test」ボタンをクリックして何も変更せずに「保存」をクリックします
その後もう一度「Test」をクリックすると関数が実行されてテストが可能となります

##6.Event BridgeによるCron日次実行
いよいよここからはPythonプログラムの定期実行をLambda上で実装していきます

---
関数の詳細画面から「トリガーを追加」をクリックします

:::note info
トリガーとは日本語で引き金という意味で、ここではLambda関数を起動させる条件を指定することになります
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/bc9bc8a3-8445-97bd-5869-f7895c2b2815.png)

---
トリガーで「Event Bridge」を選択し「新規ルールの作成」を選択します
ルール名は「daily_news」を選択しルールタイプは「スケジュール式」を選択します
ここでcron式という式を使っていつ関数を起動するか記述する必要があります
詳しくは[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html "公式ドキュメント")を参考にしてほしいですが、今回は"平日夕方18時に実行する"を定義するために **cron(00 09 ? * MON-FRI *)** と記入します

:::note warn
cronは世界標準時で記入する必要があるため、日本時間18時は世界標準時9時=00 09となります
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/7ad35a95-d6cd-0e38-63b0-fbabf9ba9731.png)

---
<code>最終形<code>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/8f3f3d08-22c8-af0c-d367-40581cadb3fc.png)

---
これで平日18時にYahooニュースのメッセージがSlackに届くはずです！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363360/84b7855c-fd1c-cee7-2683-93c521b5621b.png)

#おわりに
WebScrapingとLambdaを用いたニュース通知の実装、いかがだったでしょうか？
こんなことができるのを知っているだけでもITへの関心が高まると思います。もし記事で分かりにくい部分があったらご連絡いただけると嬉しいです。
今後もクラウド技術を使った小技を定期的に投稿しますのでぜひご一読ください！