オンライン学習サイトのドットインストールには学習時間を確認できる機能がある。……がこの時間がどうも正しくない。実際の学習時間よりだいぶ少ない。おそらく回線が不安定な環境で動画を視聴しても視聴時間に加算されない。→ 自動取得した合計と視聴時間が一致していたので、ドットインストールの視聴時間は正しかった。
正しい学習時間が欲しいのでブラウザ自動操作ツールであるpuppeteerを利用して学習時間の合計を自動で求める。
注意
自動操作ツールでのアクセスは頻度が高すぎるとサービスへ負荷を与えてアクセスブロック等の対象となるので、適切にwaitを入れてあげると良い。
戦略
①puppeteerでヘッドレスブラウザ(目に見えないブラウザ)を起動する。
②ドットインストールのユーザ認証を行う。
③ドットインストールのプロフィールページへ遷移し、受講した講座のURL一覧を取得する。
④講座ページへ遷移し、受講完了した動画の視聴時間の合計を求める。(これを③で取得したURL分繰り返す)
⑤集めたデータを以下の形で出力する。
[{lessonName:'C#入門',lessonUrl:'https://dotinstall.com/lessons/basic_csharp',completeTime:'01:49:23',incompleteTime:'00:21:01'},{......}]
実装
①npmプロジェクトを作成し、puppeteerとログイン情報入力に使用するreadline-syncをインストールする。
$ npm init -y$ npm i -S puppeteer readline-sync
②puppeteerの動作確認で、googleにアクセスしてみる。
constpuppeteer=require('puppeteer');// awaitを使うためasync関数を定義する。asyncfunctionmain(){// puppeteerのブラウザを起動する。constbrowser=awaitpuppeteer.launch();// ブラウザの新しいタブを開く。constpage=awaitbrowser.newPage();// googleにアクセスする。awaitpage.goto('https://google.com');// スクショを撮る。awaitpage.screenshot({path:'test.png'});// ブラウザを閉じる。awaitbrowser.close();}main();
上記を実行してpuppeteerからgoogleにアクセスできることを確認した。
②ログイン情報を入力させる。
ドットインストールにログインするためのログイン情報をユーザに入力させる。パスワード入力時は入力内容を出力させず履歴に残させないことがミソだ。
constpuppeteer=require('puppeteer');constreadlineSync=require('readline-sync');asyncfunctionmain(){// メールアドレスを入力させる。constmail=readlineSync.question('mail: ');// パスワードを入力させる。オプションで入力内容を出力させない。constpassword=readlineSync.question('password: ',{hideEchoBack:true});console.log(mail,password);}main();
③ドットインストールにログインする。
ドットインストールへのログインは以下の流れで行う。
ログインページへの遷移(https://dotinstall.com/login)
↓
ユーザ名とパスワードのinput欄へ自動入力する。
↓
ログインボタンを押下させる。
constpuppeteer=require('puppeteer');constreadlineSync=require('readline-sync');asyncfunctionmain(){......// ログインページへの遷移awaitpage.goto('https://dotinstall.com/login');// メールアドレスとパスワードの入力awaitpage.evaluate(text=>document.querySelector('#mail').value=text,mail);awaitpage.evaluate(text=>document.querySelector('#password').value=text,password);// ログインボタン押下awaitpage.click('#login_button');......
④受講した講座のURL一覧を求める。
ユーザ名をクリックしプロフィールページへ遷移する。
↓
各講座へのリンクのaタグのhref属性を取得する。
↓
講座のページへ遷移し時間を求める。
asyncfunctionmain(){......awaitPromise.all([page.waitForNavigation({waitUntil:['load','networkidle2']}),page.click('#login_button')]);awaitPromise.all([page.waitForNavigation({waitUntil:['load','networkidle2']}),page.click('a.user-name')]);consturls=awaitpage.evaluate(()=>{consturls=[];constaElements=document.querySelectorAll('.cardBox > h3 > a');for(constaElementofaElements){urls.push(aElement.getAttribute('href'));}returnurls;});constresult=[];for(consturlofurls){constlessonUrl='https://dotinstall.com'+url;awaitpage.goto(lessonUrl,{waitUntil:['load','networkidle2']});// 負荷軽減のため3秒待機するawaitpage.waitForTimeout(3000);constlessonName=awaitpage.$eval('.package-info-title span',element=>element.innerHTML);const[completeTime,incompleteTime]=awaitpage.evaluate(()=>{letcompleteTime=0;letincompleteTime=0;constsectionElements=document.querySelectorAll('#lessons_list > li');for(constsectionElementofsectionElements){consttime=sectionElement.querySelector('.lessons-list-title > span').innerHTML;const[,min,sec]=time.match(/\((\d\d)\:(\d\d)\)/);constseconds=parseInt(min)*60+parseInt(sec);constisCompleted=sectionElement.querySelector('.lesson_complete_button > span').innerHTML==='完了済';if(isCompleted){completeTime+=seconds;}else{incompleteTime+=seconds;}}return[completeTime,incompleteTime];});functionsec2time(sec){return`${parseInt(sec/3600)}:${parseInt((sec/60)%60)}:${sec%60}`;}result.push({lessonName,lessonUrl,completeTime:sec2time(completeTime),incompleteTime:sec2time(incompleteTime)});}console.log(result);......