LINEでBotを作成したので、できるまでの過程を残した記事になります。この記事では前回までをベースにYouTubeから動画のURLを取得し、それをLINE上に返信するBotを作成します。
皆さんも、この記事や他の記事を参考にしながら、自分のオリジナルのLINEのBotを作ってみてはいかがでしょう?
- 実際に作ったもの
- 作ろうと思ったきっかけ
- 環境や使った技術等
- 参考記事
- 前回まで
- まずはローカル環境で
- Herokuへデプロイする
- おまけ:Error R14 (Memory quota exceeded)
- おまけ:うまくデプロイできなかった場合
- 参考記事
実際に作ったもの
メッセージを送信した際に、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 selenium:3.141.0
参考記事
参考記事はこちらです。設定等が必要ですが、それの詳しいやり方は1番目の記事や2番目の記事などを参考に行ってください。
前回まで
前回はメッセージを送信してYahooの記事を取得するまでをやりました。
ここからは当初の目的であった、送ったメッセージをYouTubeで検索し、それの検索結果を取得します。
まずはローカル環境で
いきなりHerokuにデプロイしてうまくいくわけがないので、いったんローカル環境でちゃんと取って来れるかを試してみましょう。
今回はPythonでスクレイピングを行いますが、あらかじめ必要なものがあります。
それがWebDriverという物です。
このWebDriverはブラウザを操作する際に必要になります。これがあればブラウザをプログラムから自由に操作することが出来ます。やろうと思えば、画像の自動収集も可能です。
WebDriverは操作するブラウザ、例えばFireFoxならFireFoxのWebDriver、ChromeならChromeのWebDriverなどブラウザに沿ったWebDriverが必要になります。加えて、ブラウザのバージョンによっても必要なWebDriverが変わってきます。
今回はChromeを操作するので、ChromeのWebDriverをインストールしましょう。
ChromeのWebDriverのインストール
まず、Chromeのバージョンを確認しましょう。Chromeの右上にあるを押してください。
その後、「ヘルプ(H)」に行くと、「Google Chromeについて(G)」とあるので、それを押します。
すると、下図のようにChromeのバージョンが確認できます。
今回はバージョンが84.~ですので、それに合わせたWebDriverを取得します。
ChromeのWebDriverの取得はこちら
ChromeのWebDriver
WebDriverはブラウザのバージョンに合わせ、84.~から始まるものをインストールして下さい(場合によってはブラウザのバージョンが違うことがあります。その際は、その時のブラウザのバージョンに合わせて、WebDriverをインストールして下さい)。
WebDriverのインストールが終わったら、適当な場所にchromedriver.exeを置きましょう。私はLineBotというディレクトリの下にwebdriverというフォルダを作成し、その中にchromedriver.exeを置きました。イメージは下のような感じです。
LineBot\ │ ├─webdriver │ chromedriver.exe
これで、ブラウザを操作する準備は完了です。次からは実際にプログラムを書いていきましょう。
スクレイピングするプログラム scrapeYoutube.py
from selenium import webdriver from selenium.webdriver.chrome.options import Options import time def getMusic(searchWord): options = Options() # options.add_argument('--headless'); # ※ヘッドレスモードを使用する場合、コメントアウトを外す DRIVER_PATH = 'webdriver\chromedriver.exe' #local用 driver = webdriver.Chrome(executable_path=DRIVER_PATH) #local用 driver.get("https://www.youtube.com/results?search_query={:}".format(searchWord)) time.sleep(1)#1秒待つ element = driver.find_elements_by_class_name("style-scope ytd-video-renderer") count = 0 hrefUrl = [] for e in element: url = e.find_element_by_id("thumbnail") hrefUrl.append(url.get_attribute("href")) count = count + 1 if 3 <= count: #3曲に絞る break if count == 0: hrefUrl.append("音楽が見つかりませんでした!!") result = '\n'.join(hrefUrl) return result
上記プログラムの
DRIVER_PATH = 'webdriver\chromedriver.exe' #local用 driver = webdriver.Chrome(executable_path=DRIVER_PATH) #local用
でchromedriver.exeを置いたパスを書きます。相対パスでも絶対パスのどちらでも大丈夫です。
driver.get("https://www.youtube.com/results?search_query={:}".format(searchWord))
この行では、メッセージとして受け取ったsearchWordをYouTubeで検索します。
で、なぜ上のURLになっているかというと、YouTube上で例えば「洋楽」で検索した場合、YouTube上でのURLは次のようになります。
このような理由で、上のようなURLをdriver.get内に指定しています。
取得する範囲を指定する
さて、実際に取得したいのは、下の画像のように赤で囲まれた部分です。
そこで、前回と同様に右クリックを押して、「検証」で調べてみましょう。
すると、下図のようにclass名が[style-scope ytd-video-renderer」となっている部分で色が変化していることが分かります。
どうやらclass名が[style-scope ytd-video-renderer」となっている部分を取ってくれば良さそうです。加えて、これを複数取得する必要があります。
というわけで、
element = driver.find_elements_by_class_name("style-scope ytd-video-renderer")
とすることで、クラス名が"style-scope ytd-video-renderer"となっている部分だけを取得することが出来ます。ここで、メソッド名を
driver.driver.find_element_by_class_name("style-scope ytd-video-renderer")
とする(elementsのsがない)と、一つしか取れないので注意しましょう。
動画のURLを取得する
さて、ここまでで指定した部分の要素を取得することが出来ました。ここからさらに動画のURLを取得する必要があります。
そのURLがどこに書かれているかというと、下の画像のaタグの中にあります。
ここでは、idを指定して取得することにします。find_element_by_idメソッドを利用して、一旦urlという変数に入れ、次にhref属性の値をget_attributeというメソッドで取得します。最後にhrefUrlというリストに追加していきます。
url = e.find_element_by_id("thumbnail") hrefUrl.append(url.get_attribute("href"))
以上で、scrapeYoutube.pyの簡単な説明は終了です。
メインプログラム local.py
import scrapeYoutube as scy if __name__ == "__main__": word = "洋楽" result = scy.getMusic(word) print(result)
wordに「洋楽」という言葉を入れて、先ほどのscrapeYoutube.pyのメソッドに渡し、検索結果をresultに格納して出力しているだけです。
これを以下のようにして実行すると、
$ python local.py
という風にURLを取得することが出来ました。
Herokuへデプロイする
ローカル環境で取得することが出来たので、今度はHerokuへのデプロイをやってみましょう。
Heroku上でのWebDriverの指定
ローカル環境ではWebDriverをwebdriverというディレクトリを作ってその下に入れました。しかし、このままデプロイしてもHerokuで動いてくれるわけではないので、HerokuでWebDriverを設定する必要があります。
こちらの記事にHeroku上でのWebDriverの指定方法が書かれているので、こちらを参考にWebDriverを指定します。
HerokuでWebDriverを指定する記事:
Heroku + Selenium + ChromeでWEBプロセスを自動化する
フォルダの構成
C:. │ .gitignore │ main.py │ Procfile │ requirements.txt │ runtime.txt │ scrapeYoutube.py │ ├─webdriver │ chromedriver.exe │
WebDriverはローカル環境用なので本当はいらないのですが、念のため残しました。
ちなみに、Windowsだと
$ tree /f
とコマンドプロンプトに打つことで表示させることが出来ます。
main.py
import os import json import scrapeYoutube as scy 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 = scy.getMusic(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)
scrapeYoutube.py
from selenium import webdriver from selenium.webdriver.chrome.options import Options import time def getMusic(searchWord): options = Options() options.add_argument('--headless'); # ※ヘッドレスモードを使用する場合、コメントアウトを外す driver = webdriver.Chrome(options=options) # heroku用 driver.get("https://www.youtube.com/results?search_query={:}".format(searchWord)) time.sleep(1) element = driver.find_elements_by_class_name("style-scope ytd-video-renderer") count = 0 hrefUrl = [] for e in element: url = e.find_element_by_id("thumbnail") print(url.get_attribute("href")) hrefUrl.append(url.get_attribute("href")) count = count + 1 if 3 <= count: #3曲に絞る break if count == 0: hrefUrl.append("音楽が見つかりませんでした!!") result = '\n'.join(hrefUrl) 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 selenium==3.141.0
Herokuへデプロイする
$ git add . $ git commit -m "メッセージ" $ git push heroku master
上から順にコマンドで打ち込んでください。Heroku上でWebDriverの設定を行ったため、ビルドまでに多少時間がかかります。
デプロイが終わったら、LINEにメッセージを送ってみましょう。
きちんとできていれば、下の画像のように動画のURLを取得できるはずです。
おまけ:Error R14 (Memory quota exceeded)
Herokuを無料プランで使う場合、メモリは512MBしか使えません。そのため、メッセージを連続で送ると、
Error R14 (Memory quota exceeded)
が出てしまうことがあります。メモリーエラーのため、しばらくすれば再度メッセージを送ることが出来ます。もし心配であれば、下のコマンドを打ち込んでください。
$ heroku config:set WEB_CONCURRENCY=1
このコマンドを打つことで多少挙動は良くなりますが、連続でメッセージを送信するとR14のメモリーエラーが出てしまいます。
もしHerokuを使ってデプロイを行う場合、無料プランでは処理の重いAPIは使わないようにしましょう。
おまけ:うまくデプロイできなかった場合
場合によってはうまくデプロイできない場合もあります。その時は、ターミナル上で
$ heroku logs --tail
と打ち込んでください。ログを見ることが出来るので、どこで失敗しているのかを見つけることが可能です。
例えば、main.pyで
# import os import json import scrapeYoutube as scy
上記のようにimport os
の部分を追加し忘れてしまった時、メッセージを送っても動画のURLは取得できません。そんな時は
$ heroku logs --tail
と打ちましょう。すると下図のように、何かしらのエラーが出ていることが分かります。
これを詳しく見てみると、
osが定義されていないというNameErrorを見ることが出来ます。
このようにして、エラーを発見し修正した後は、先ほどと同じように
$ git add 修正したファイル名 $ git commit -m "メッセージ" $ git push heroku master
として、Herokuへデプロイしましょう。
参考記事
LINE Bot作成
1時間でWikipedia検索できるLINE BOTをサクッと作ってみよう!
PythonとLINE APIとHerokuでBOTを作る【Python編】
Heroku上へWebDriverの指定
Heroku + Selenium + ChromeでWEBプロセスを自動化する