LINEでBotを作成したので、できるまでの過程を残した記事になります。実際に作ったものはメッセージを送信すると、そのメッセージをYouTubeで検索して、検索結果のURLを取得するという物です。しかし、この記事では、お試しとしてYahoo!ニュースのトップ記事を取得するところから始めます。
実際に作ったBotの詳細は次回紹介します。
実際に作ったもの
メッセージを送信した際に、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番目の記事からWikipedia検索までをやります。1番目の記事はWikipediaのAPIを使って検索結果を返してくれます。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!ニュースのトップ記事に行ったらそこで右クリックを押します。すると、「検証」という項目が出てくるので、検証を押しましょう。
そうすると、下記のような画像になるはずです。
ここで、下に出ている部分にマウスを移動すると
htmlで書かれている部分とそれに対応する部分の色が変わります。これをもとにトピック記事にあたる部分を探します。
すると、htmlで書かれている
<div class="topicsListAllMain">
の部分がトピック記事にあたるようです。(下図参照)
てなわけで、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
を順に行います。
デプロイ後、メッセージを送れば
とこんな感じで記事を取得出来ます。