プログラムで生活をHACKする

YRen-LaB

Python Selenium

Selenium で youtubeからサムネイルと動画キャプチャを取得する

投稿日:


経緯

趣味でモザイクアートをPythonで作成するプログラムを書いていて、素材が大量に必要になった。
元画像はとあるVtuberのものを使用する気でいたので、どうせならその人のサムネとキャプチャで
作成すると思った。

でも手で毎回キャプチャするのは論外として、Bandicamのようなツールを使おうとしても毎回動画を
切り替えるのもめんどくさい。

なら自分で作るかという経緯。

処理の流れ

  1. 対象youtube の動画一覧ページのURLをinputとして渡す
      例)https://www.youtube.com/channel/xxxxxxxxxxxxx/videos
  2. 下にひたすらスクロール。上記URLでは下にスクロールしないと古い動画が表示されない。
    そのため新しい動画が表示されなくなるまでスクロールを続ける
    1.全動画のサムネと動画へのURLを取得する
    この時のサムネの画像サイズは336×188だが素材画像はどうせリサイズするのでこのまま保存
  3. 動画のURLの数だけ以下をループ
    1. 動画ページへ遷移
    2. (初回のみ)音量オフ
    3. 広告スキップ
    4. フルスクリーンにする
    5. pyautoguiを使用しシークバーを移動させる
    6. キャプチャを撮る

実装

以下実際の実装

# coding:utf-8
import configparser
import datetime
from time import sleep  # ブラウザ待機用

import pyautogui
import requests
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

import common

###################設定###################

# 設定ファイルから情報を取得
config = configparser.RawConfigParser()
config.read('appSetting.ini',encoding="utf-8")

# 1動画当たりから何枚のキャプチャを撮るか
thumbnail_cap_count = 50

# フルスクにした時のシークの開始位置
x_start,y_start = (-1895,1024)

# フルスクにした時のシークの最終位置
x_end,y_end = (-28,1020)

##########################################

'''
# youtubeのサムネと動画URLを取得する
'''
def __get_thumbnail(browser,is_get_thumbnail):
    videos = []
    # 新しい動画が出てこなくなるまでスクロールしまくる
    for i in range(30):
        browser.find_element_by_tag_name('body').send_keys(Keys.END) # Use send_keys(Keys.HOME) to scroll up to the top of page    # サムネ取得
        common.__wait_browser(browser)
    video_doms = browser.find_element_by_xpath("//ytd-grid-renderer[@class=\"style-scope ytd-item-section-renderer\"]")
    video_dom = video_doms.find_elements_by_id("dismissable")
    for video in video_dom:

        # inputが0ならサムネを取得する
        if is_get_thumbnail == "0":
            thumbnail = video.find_element_by_tag_name("img").get_attribute("src")
            # サムネイル保存
            request = requests.get(thumbnail)
            date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
            with open(config.get("path","thumbnail_paht") + '\\' + date + ".jpg" ,'wb') as f:
                f.write(request.content)
                sleep(1)

        # 動画のURLを取得
        video_url = video.find_element_by_id("video-title").get_attribute("href")
        videos.append(video_url)
    return videos

'''
# 動画をキャプチャする
'''
def __video_capture(browser,video_urls):
    volume_off = True
    for video_url in video_urls:

        # 動画ページへ遷移
        browser.get(video_url)
        common.__wait_browser(browser)

        # 音量オフ
        if volume_off:
            browser.find_element_by_xpath("//button[@class=\"ytp-mute-button ytp-button\"]").click()
            volume_off = False

        # 広告スキップ
        __skip_advertising(browser)

        # フルスクリーンをクリック
        browser.find_element_by_xpath("//button[@class=\"ytp-fullscreen-button ytp-button\"]").click()

        # 画面キャプチャ
        # 撮る枚数からシークバーの移動幅を計算
        move_seek = ((x_end) + (-x_start)) / thumbnail_cap_count

        # pyautoguiを使用してシークバーを20回移動しキャプチャする
        for i in range(thumbnail_cap_count):
            pyautogui.moveTo(x_start + (move_seek * i), y_start)
            pyautogui.click()
            common.__wait_browser(browser)
            __skip_advertising(browser)# 広告が入った場合はスキップ

            # キャプチャ
            video_item = browser.find_element_by_xpath("//video[@class=\"video-stream html5-main-video\"]").screenshot_as_png
            with open(config.get("path","screen_shot") + "/" +datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".png", "wb") as f:
                f.write(video_item)

def __skip_advertising(browser):
    # 広告が出たらスキップ
    if len(browser.find_elements_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]")) > 0:
        sleep(5) # 広告がSkipできるまで待機
        browser.find_element_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]").click()
        common.__wait_browser(browser)

        # 連続で広告が出てきた場合は再帰でスキップ
        if len(browser.find_elements_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]")) > 0:
            __skip_advertising(browser)

if __name__ == "__main__":

    #selenium用の設定もろもろ
    webdriver_path = config.get("web_driver","chrome_driver")
    browser = webdriver.Chrome(executable_path=webdriver_path)

    thumbneil_url = input("サムネと動画を取得するyoutubeのURL\n")
    is_get_thumbnail = input("サムネを取得するか 0:取得する  1:取得しない\n")
    browser.get(thumbneil_url)
    common.__wait_browser(browser)

    video_urls = __get_thumbnail(browser,is_get_thumbnail)
    # デバッグ用
    # del video_urls[0:166]

    __video_capture(browser,video_urls)

解説

①動画ページへ遷移後の動作

    # 新しい動画が出てこなくなるまでスクロールしまくる
    for i in range(30):
        browser.find_element_by_tag_name('body').send_keys(Keys.END) # Use send_keys(Keys.HOME) to scroll up to the top of page    # サムネ取得
        common.__wait_browser(browser)
    video_doms = browser.find_element_by_xpath("//ytd-grid-renderer[@class=\"style-scope ytd-item-section-renderer\"]")
    video_dom = video_doms.find_elements_by_id("dismissable")

流れの項目で記載しているが、youtubeの動画一覧ページは下にスクロールすることで過去の動画が表示されるようになっている。
つまり、遷移だけでは全動画の要素を所得することはできない。

そこで、'body'に対して.send_keysでキーボードの「END」キーを連続で送っている。
30回というのは適当。動画数が多い場合はもっと増やせば良い。

    video_doms = browser.find_element_by_xpath("//ytd-grid-renderer[@class=\"style-scope ytd-item-section-renderer\"]")

が全動画のコンテナ要素

    video_dom = video_doms.find_elements_by_id("dismissable")

が各動画の要素

②動画のサムネイルとURLを取得

    for video in video_dom:

        # inputが0ならサムネを取得する
        if is_get_thumbnail == "0":
            thumbnail = video.find_element_by_tag_name("img").get_attribute("src")
            # サムネイル保存
            request = requests.get(thumbnail)
            date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
            with open(config.get("path","thumbnail_paht") + '\\' + date + ".jpg" ,'wb') as f:
                f.write(request.content)
                sleep(1)

        # 動画のURLを取得
        video_url = video.find_element_by_id("video-title").get_attribute("href")
        videos.append(video_url)
    return videos

①で取得した各動画の要素分ループする。

            thumbnail = video.find_element_by_tag_name("img").get_attribute("src")
            # サムネイル保存
            request = requests.get(thumbnail)
            date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
            with open(config.get("path","thumbnail_paht") + '\\' + date + ".jpg" ,'wb') as f:
                f.write(request.content)
                sleep(1)

サムネを取得し、時分秒をファイル名として保存する。
1秒スリーブしているのは特に意味は無し。
動画一覧のサムネは336×188のサイズ。

        # 動画のURLを取得
        video_url = video.find_element_by_id("video-title").get_attribute("href")
        videos.append(video_url)

動画のURLを取得しListに詰めている。

③キャプチャを撮る

音量をオフにする

        # 音量オフ
        if volume_off:
            browser.find_element_by_xpath("//button[@class=\"ytp-mute-button ytp-button\"]").click()
            volume_off = False

動画ページへ遷移すると動画が再生されるが、音がでかい。というか
キャプチャが欲しいのに音はいらないためオフにする
幸い音量ボタンはButtonのため.click()でオフとなる。
また、
volume_offフラグをFalseにすることで2ループ目以降は音量オフのままキャプチャを取得する

広告スキップ

    # 広告が出たらスキップ
    if len(browser.find_elements_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]")) > 0:
        sleep(5) # 広告がSkipできるまで待機
        browser.find_element_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]").click()
        common.__wait_browser(browser)

        # 連続で広告が出てきた場合は再帰でスキップ
        if len(browser.find_elements_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]")) > 0:
            __skip_advertising(browser)

youtube再生に付き物なのが広告。あれをスキップする。
広告のスキップボタンにも要素がちゃんとある。
HTML上に「ytp-ad-skip-button ytp-button」があるか判定し、スキップが押下できるまでの5秒間を待機後
上記要素を.click()することで広告のスキップが可能。

まれに2連続、3連続で広告が表示されるが、当メソッドを再帰処理することで対応している。

キャプチャを撮るためにフルスクリーンにする

        # フルスクリーンをクリック
        browser.find_element_by_xpath("//button[@class=\"ytp-fullscreen-button ytp-button\"]").click()

youtbeのフルスクリーンボタンの要素を.click()してるだけ。

シークバーを操作し、キャプチャを撮る

        # 画面キャプチャ
        # 撮る枚数からシークバーの移動幅を計算
        move_seek = ((x_end) + (-x_start)) / thumbnail_cap_count

        # pyautoguiを使用してシークバーを移動しキャプチャする
        for i in range(thumbnail_cap_count):
            pyautogui.moveTo(x_start + (move_seek * i), y_start)
            pyautogui.click()
            common.__wait_browser(browser)
            __skip_advertising(browser)# 広告が入った場合はスキップ

            # キャプチャ
            video_item = browser.find_element_by_xpath("//video[@class=\"video-stream html5-main-video\"]").screenshot_as_png
            with open(config.get("path","screen_shot") + "/" +datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".png", "wb") as f:
                f.write(video_item)

ここではpyautoguiを使用してマウスポインタを移動し、脳筋的に処理している。
pyautoguiについて勉強しとことは後ほど別記事に記載予定(多分)

        # 撮る枚数からシークバーの移動幅を計算
        move_seek = ((x_end) + (-x_start)) / thumbnail_cap_count

シークバーの初めの座標と最後の座標をキャプチャ枚数で分割し、区切る座標を計算する

        # pyautoguiを使用してシークバーを移動しキャプチャする
        for i in range(thumbnail_cap_count):
            pyautogui.moveTo(x_start + (move_seek * i), y_start)
            pyautogui.click()
            common.__wait_browser(browser)
            __skip_advertising(browser)# 広告が入った場合はスキップ

キャプチャする数だけループし
pyautogui.moveTo()で上記で計算した座標を足していき、クリックしている。
もし広告が入った場合はスキップする
(実はこれだと50枚のキャプチャ指定で49枚しか取れないけどまあええやろ...)

            # キャプチャ
            video_item = browser.find_element_by_xpath("//video[@class=\"video-stream html5-main-video\"]").screenshot_as_png
            with open(config.get("path","screen_shot") + "/" +datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".png", "wb") as f:
                f.write(video_item)

キャプチャを撮る。
Seleniumでキャプチャを撮るには.screenshot_as_pngが使用できる。
それを日時をファイル名にして保存

まとめ

おそらく使う人はいないであろうツール。
これの最大の問題がpyautoguiのマウス座標を使用してしまっていること。
これを使用しているせいでこのツールを使用中は他の作業ができない。(マウスカーソルが持っていかれるため)

外部ライブラリでストリーミング動画を扱えるようなものを見つけれれば良かったが
私には見つけられなかった。
もしお勧めのものがあればご教授いただければ幸いです。

いずれpywinautoでマウス座標を使わないように使用と思います

-Python, Selenium
-, ,

Copyright© YRen-LaB , 2024 AllRights Reserved Powered by AFFINGER4.