とあるお兄さんの雑記

基本的に技術系の内容を書きますが、何を書くかは私の気分です。

LINEで自分用オリジナルBotを作成するまで~Yahoo!ニュースのトップ記事検索編~

LINEでBotを作成したので、できるまでの過程を残した記事になります。実際に作ったものはメッセージを送信すると、そのメッセージをYouTubeで検索して、検索結果のURLを取得するという物です。しかし、この記事では、お試しとしてYahoo!ニュースのトップ記事を取得するところから始めます。

実際に作ったBotの詳細は次回紹介します。

実際に作ったもの

f:id:kurasher:20200729200739j:plain

メッセージを送信した際に、YouTubeに飛んでメッセージを検索し、その検索結果から上から3つまでを取ってくるようにしたBotです。

作ろうと思ったきっかけ

これを作ってみようと思ったきっかけは、朝起きるのが面倒だったことから始まります。

私は朝起きるのが面倒で時間が許すならいつまでも寝ていたい派です。そこで、どうにかして起きれないかと思い音楽を流してテンションを上げようと思いました。しかし、ただ、YouTubeで探して流しても面白くないので、せっかくだったらBotを使ってみようということになりました。

環境や使った技術等

Windows 10
Heroku
LINE Messaging API
Python: 3.7.7
Flask:0.12.2
line-bot-sdk:1.5.0
beautifulsoup4:4.7
soupsieve:1.6.1
urllib3:1.24.1

参考記事

参考記事はこちらです。設定等が必要ですが、それの詳しいやり方は1番目の記事や2番目の記事などを参考に行ってください。

  1. 1時間でWikipedia検索できるLINE BOTをサクッと作ってみよう!

  2. PythonとLINE APIとHerokuでBOTを作る【Python編】

まずは上記の1番目の記事からWikipedia検索までをやります。1番目の記事はWikipediaAPIを使って検索結果を返してくれます。APIを使っているため、Wikipediaの検索までは簡単にできます。その後、いろいろAPIを使えるやつがないかを調べたりしましたが、いまいちピンとくるものが無くてやめました。

次に2番目の記事でYahoo!ニュースのトップ記事から検索キーワードに引っかかった記事を返します。基本的には、1と2番目の記事を参考にファイルを用意していきます。基本コピペで問題ありません。

で、作ってたのはいいのですが、1番目の記事を流用して作っていたためかなかなか記事を返してくれませんでした。次からは私が詰まった部分から話をしていきます。

詰まったところ:記事を返してくれない...

なぜか、理由は分かりませんが、記事を返してくれませんでした。というわけで、いったんローカルで動かしてみることにしました。

メインのプログラムはこちら

localMain.py

import urllib.request
import os
import sys
import json
import scrape as sc
from argparse import ArgumentParser

from flask import Flask, request, abort

if __name__ == "__main__":
    word = input()
    result = sc.getNews(word)
    print(result)



実際にスクレイピングするプログラムはこちら

scrape.py

from bs4 import BeautifulSoup
import urllib.request
import json
import requests

url = 'https://news.yahoo.co.jp/topics'
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) '\
     'AppleWebKit/537.36 (KHTML, like Gecko) '\
     'Chrome/67.0.3396.99 Safari/537.36 '

def getNews(word):
    req = urllib.request.Request(url, headers={'User-Agent': ua})
    print("req ", req)
    html = urllib.request.urlopen(req)
    print("html ", html)
    soup = BeautifulSoup(html, "html.parser")
    print("soup ", soup)
    main = soup.find('div', attrs={'class': 'topicsMod'})
    print("main = ", main)
    topics = main.select("li > a")
    print("topics", topics)

    count = 0
    list = []

    for topic in topics:
        if topic.contents[0].find(word) > -1:
            list.append(topic.contents[0])
            list.append(topic.get('href'))
            count += 1
    if count == 0:
        list.append("記事が見つかりませんでした!!")

    result = '\n'.join(list)
    return result



あとは、ターミナル上で、

$ python localMain.py

と実行すれば、記事のタイトルやURLを含めたものを返してくれるのですが、scrape.pyの

    main = soup.find('div', attrs={'class': 'topicsMod'})
    print("main = ", main)

の部分の出力が

main =  None

となっていました。なんで?

というわけで、実際にYahoo!ニュースのトップ記事に行って調べてみましょう。

Yahoo!ニュースのトップ記事に行ったらそこで右クリックを押します。すると、「検証」という項目が出てくるので、検証を押しましょう。

そうすると、下記のような画像になるはずです。

f:id:kurasher:20200729213429p:plain

ここで、下に出ている部分にマウスを移動すると

f:id:kurasher:20200729213713p:plain

htmlで書かれている部分とそれに対応する部分の色が変わります。これをもとにトピック記事にあたる部分を探します。

すると、htmlで書かれている

<div class="topicsListAllMain">

の部分がトピック記事にあたるようです。(下図参照)

f:id:kurasher:20200729214113p:plain



てなわけで、scrape.pyの

    main = soup.find('div', attrs={'class': 'topicsMod'})
    print("main = ", main)

    main = soup.find('div', attrs={'class': 'topicsListAllMain'})
    print("main = ", main)

に変更します。

これで、localMain.pyを実行すれば合致する記事とそのURLを取得することが出来ます。ローカル環境でうまくいったので、これをLINEのBotで返すように変更します。

LINE Botで記事を取得する

以下のファイルを用意して、中身もコピペすれば大丈夫なはずです。

main.py
scrape.py
Procfile
runtime.txt
requirements.txt

main.py

import urllib.request
import os
import sys
import json
import scrape as sc
from argparse import ArgumentParser

from flask import Flask, request, abort
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)

YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]


line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)


    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):

    word = event.message.text
    result = sc.getNews(word)

    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=result)
    )

if __name__ == "__main__":
    port = int(os.getenv("PORT", 8000))
    app.run(host="0.0.0.0", port=port)

main.pyのcallback()メソッドは基本的にいじりません。handle_message(event)メソッドのほうで行いたい処理を書いていきます。

scrape.py

from bs4 import BeautifulSoup
import urllib.request
import json
import requests

# 参考URL : https://shikasen-engineer.com/python_line_bot/
url = 'https://news.yahoo.co.jp/topics'
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) '\
     'AppleWebKit/537.36 (KHTML, like Gecko) '\
     'Chrome/67.0.3396.99 Safari/537.36 '

def getNews(word):
    req = urllib.request.Request(url, headers={'User-Agent': ua})
    html = urllib.request.urlopen(req)
    soup = BeautifulSoup(html, "html.parser")
    main = soup.find('div', attrs={'class': 'topicsListAllMain'})
    topics = main.select("li > a")

    count = 0
    list = []

    for topic in topics:
        if topic.contents[0].find(word) > -1:
            list.append(topic.contents[0])
            list.append(topic.get('href'))
            count += 1
    if count == 0:
        list.append("記事が見つかりませんでした!!")

    result = '\n'.join(list)
    return result

Procfile

拡張子は指定しないでください。

# Procfile
web: python main.py

runtime.txt

python-3.7.7

requirements.txt

Flask==0.12.2
line-bot-sdk==1.5.0
beautifulsoup4==4.7.
soupsieve==1.6.1
urllib3==1.24.1

これらを用意したらHerokuへデプロイします。こうすることで、LINEにメッセージを送れば、メッセージに合致した記事を検索してきます。



Herokuへのデプロイ方法は

$ git add .

$ git commit -m "メッセージ"

$ git push heroku master

を順に行います。

デプロイ後、メッセージを送れば

f:id:kurasher:20200729221834p:plain

とこんな感じで記事を取得出来ます。