とあるお兄さんの雑記

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

LINEで自分用オリジナルBotを作成するまで~YouTubeから検索結果のURL取得編~

LINEでBotを作成したので、できるまでの過程を残した記事になります。この記事では前回までをベースにYouTubeから動画のURLを取得し、それをLINE上に返信するBotを作成します。

皆さんも、この記事や他の記事を参考にしながら、自分のオリジナルのLINEの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
selenium:3.141.0

参考記事

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

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

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

前回まで

前回はメッセージを送信してYahooの記事を取得するまでをやりました。

ここからは当初の目的であった、送ったメッセージをYouTubeで検索し、それの検索結果を取得します。

まずはローカル環境で

いきなりHerokuにデプロイしてうまくいくわけがないので、いったんローカル環境でちゃんと取って来れるかを試してみましょう。

今回はPythonスクレイピングを行いますが、あらかじめ必要なものがあります。
それがWebDriverという物です。

このWebDriverはブラウザを操作する際に必要になります。これがあればブラウザをプログラムから自由に操作することが出来ます。やろうと思えば、画像の自動収集も可能です。

WebDriverは操作するブラウザ、例えばFireFoxならFireFoxのWebDriver、ChromeならChromeのWebDriverなどブラウザに沿ったWebDriverが必要になります。加えて、ブラウザのバージョンによっても必要なWebDriverが変わってきます。

今回はChromeを操作するので、ChromeのWebDriverをインストールしましょう。

ChromeのWebDriverのインストール

まず、Chromeのバージョンを確認しましょう。Chromeの右上にある \vdotsを押してください。

f:id:kurasher:20200731234514p:plain

その後、「ヘルプ(H)」に行くと、「Google Chromeについて(G)」とあるので、それを押します。

すると、下図のようにChromeのバージョンが確認できます。

f:id:kurasher:20200731234838p:plain

今回はバージョンが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は次のようになります。

f:id:kurasher:20200801001744p:plain

このような理由で、上のようなURLをdriver.get内に指定しています。

取得する範囲を指定する

さて、実際に取得したいのは、下の画像のように赤で囲まれた部分です。

f:id:kurasher:20200801003112p:plain

そこで、前回と同様に右クリックを押して、「検証」で調べてみましょう。

すると、下図のようにclass名が[style-scope ytd-video-renderer」となっている部分で色が変化していることが分かります。

f:id:kurasher:20200801005153p:plain

どうやら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タグの中にあります。

f:id:kurasher:20200801010543p:plain

ここでは、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

f:id:kurasher:20200801012202p:plain

という風に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を取得できるはずです。

f:id:kurasher:20200729200739j:plain

おまけ: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

と打ちましょう。すると下図のように、何かしらのエラーが出ていることが分かります。

f:id:kurasher:20200801020530p:plain

これを詳しく見てみると、

f:id:kurasher:20200801020755p:plain

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プロセスを自動化する

Selenium
Seleniumクイックリファレンス