薬剤師のプログラミング学習日記

プログラミングやコンピュータに関する記事を書いていきます

【DOM操作】研修動画の再生ボタンを自動化してみた

以前、薬剤師向け研修用動画サイト(MPラーニング)の操作を自動化する記事を書きました。当時は OpenCV を使った「画像認識による自動クリック」という方法(力技)でしたが、今回はより技術的なアプローチであるDOM操作(JavaScript)での自動化に挑戦しました。

 

なぜ自動化したくなったのか?

動機をひと言でいうと、何度も再生ボタンを押すのが面倒くさかったからです。この研修動画とは概ね次のようなものです。

  • 1コンテンツ30分程度
  • スケジュール(見出し)が変わるごとに止まる
  • そのたびに「次の学習へ」や「▶」を押す必要がある

 

この記事で書くこと

この記事では、

  • 画像認識から DOM *1 操作への切り替え
  • 自動操作する際にハマったポイント
  • iframe やブラウザの動画再生制限

などの試行錯誤をまとめようと思います。
一応前提として、視聴時間のスキップや不正アクセス等の規約違反行為をするというようなことはしていません。あくまで「ボタンを押す手間」の自動化が主題です。
コードは生成AIに相談しながら作成しました。
 

実行環境

  • Windows11
  • Google Chrome
  • Chrome拡張:Tampermonkey

 

以前の方法(画像認識)の問題点

以前の記事では次のような方法を使っていました。

  • クリックしたいボタンを画像として保存
  • OpenCV でテンプレートマッチング
  • 一致したらクリック

この方法は一見シンプルですが、

  • 背景や解像度が変わると失敗する
  • 数秒おきにチェックするため無駄に重い
  • Webページなのに画像として扱っている

という違和感(無理やり感)がありました。過去記事はこちらです。
www.yakupro.info
そこで、今回はWebページの構造(DOM)を監視し、対象の要素(ボタン)が描画されたらクリックイベントを実行する、という真っ当なアプローチに変更することにしました。
 

Tampermonkeyについて

Tampermonkeyは公式ウェブサイトからインストールできるChrome拡張機能で、これを使うことで特定のURLに対して自作のJavaScriptを自動的に実行*2することができます。
chromewebstore.google.com

インストールしたら、拡張の管理画面で「ユーザースクリプトを許可する」をオンにしておきます。
 

ハマったポイント

動画本体は別ドメイン(再生処理は iframe 内)

開発者ツールでHTMLを確認していくと、動画プレイヤーは別ドメインのiframeとして読み込まれていました。セキュリティの制約(同一生成元ポリシー(Same-Origin Policy))により、親ページからiframeの中身を直接操作することはできないため、「次の学習へ」のボタンがある親ページ用とiframe用のスクリプトを分割して適用させる必要がありました。

 

人間がクリックするのとプログラムの click は違う

再生ボタン(▶)が出てきた後、人間がクリックすると再生されるのに、iframe用のプログラムによる.click()を実行しても動画は再生されませんでした。
調べてみると、近年のChromeなどのブラウザはユーザーの物理的な操作(Trusted Event)を伴わない、音声付き動画の自動再生をブロックする仕様(Autoplay Policy)になっているようです。
最終的には2回クリック処理*3を行った後で、muted(消音)状態にすることでブラウザに再生を許可させることに成功しました。
ただ、この再生はmuted(消音)なので結局音量ボタンを自分で操作する必要が出てきてしまいました。
 

再生速度を変えると戻される

Youtubeで倍速再生に慣れてる身としては、あわよくばと思って試してみたのですが×1.25などの倍速にすると、ボタンの自動クリックが行われても次の学習に進まず、再び同じスケジュールの冒頭に戻されてしまいました。
これについては公式ページに次のような記述がありました。

A9. 受講の進捗が保存されません。
MPラーニングでは受講時に再生時間の計測を行い、コンテンツの各項目終了ごとに「次の学習へ」を押して進むことで進捗が保存されます。
~(一部略)~
アプリやブラウザの倍速機能を利用し、再生していませんか?

書いてあるとおり、サーバー側では実際の経過時間で視聴状況を判定していたため、ということですね。
 

まとめ

今回の自動化を通して、

  • Webページ操作は、画像認識よりも DOM操作の方が圧倒的に軽量かつ安定的
  • iframeやクロスドメインの制約を理解する必要がある
  • 昨今のブラウザは、ユーザー体験保護のために自動再生を厳しく制限している

ということを改めて実感しました。
 
最後に今回のコードを載せておきます。
 
上でも書きましたが、このプログラムによって自動クリックをすると音量を触る必要があるという、結局やる前と大差ない結果にはなってしまいました。まあこれはこれで、これ以上できることはないと理解できたので満足しました。
もちろん私はきちんと自動再生されるたびに音量を上げ直して視聴したいと思います。


※クリックしてコード全体を表示

// ==UserScript==
// @name         MPラーニング 自動再生
// @namespace    mp-learning-auto
// @version      2.0
// @description  iframe動画を自動再生し、完了後に「次の学習へ」を押す
// @match        https://www.mp-learning.com/*
// @match        https://*.eq.webcdn.stream.ne.jp/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    /* ===============================
       iframe側(動画プレイヤー)
       =============================== */
    if (location.host.includes('eq.webcdn.stream.ne.jp')) {

        let clickCount = 0;
        const timer = setInterval(() => {
            const btn = document.querySelector('.eqp-big-play-button');
            if (!btn) return;

            clickCount++;
            btn.click();
            console.log(`[MP iframe] ▶クリック ${clickCount}回目`);

            if (clickCount >= 2) {
                const video = document.querySelector('video');
                if (video) {
                    video.muted = true;// Chrome自動再生対策(必須)
                    video.playbackRate = 1.0;// 速度変更しない(重要)
                    video.play().catch(()=>{});
                    console.log('[MP iframe] 再生開始');
                }
                clearInterval(timer);
            }
        }, 800);
    }

    /* ===============================
       親ページ側(次の学習へ)
       =============================== */
    if (location.host.includes('mp-learning.com')) {

        let done = false;
        
        // 可視性チェック
        function visible(el) {
            return el &&
                el.offsetParent !== null &&
                getComputedStyle(el).visibility !== 'hidden' &&
                getComputedStyle(el).opacity !== '0';
        }
        // DOMの変化を監視して検知する
        const observer = new MutationObserver(() => {
            if (done) return;

            const nextBtn = document.getElementById('btn-next-study-link');
            if (!visible(nextBtn)) return;

            done = true;
            console.log('[MP] 次の学習へ をクリック');

            setTimeout(() => {
                nextBtn.click();
                observer.disconnect();
            }, 500);
        });

        // body が存在するまで待つ(安全策)
        const wait = setInterval(() => {
            if (!document.body) return;
            clearInterval(wait);
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
            console.log('[MP] 自動次へ監視開始');
        }, 300);
    }

})();

*1:Document Object Model:HTMLなどのWebページをプログラムから操作できるようにする仕組み

*2:スクリプトは必ず自分が理解しているものだけを使うようにしてください

*3:2回じゃないと1番目の動画の再生が始まりませんでした