3
3

Seleniumなしで直接WebDriverを操作してブラウザ画面をキャプチャするスクリプトをささっと作る

Last updated at Posted at 2023-12-01

2024年9月14日現在,起動させると「msxml3.dll: 指定されたリソースが見つかりません。」というエラーメッセージが表示されて正常に動作しません。

0. はじめに

会社の e-learning が面倒だ。ブラウザで数十枚のスライドを見た後でテストがあるので,スライドを飛ばして見ると後で後悔する。以前は,自作のプログラムでブラウザ画面をキャプチャしていたのだが,Windows10 以降キャプチャできなくなった。FireFox の画面はキャプチャできるけど Edge の画面はできないので,Edge は GPU を使ってるとか何とかのせいだろう・・・

ところが世の中には WebDriver というものがあることを知った。おそらく一般的な用途としてはブラウザ操作の自動化だと思うが,その中にブラウザ画面のスクリーンショットを取る機能があることを知り,これは使えないかと思った次第である。

1. 基本方針

対象ブラウザは Edge(Chromium版)である。Edge 用の WebDriver(msedgedriver)はマイクロソフト社謹製なので会社でも使えるが,Selenium は使用不可。なので WebDriver を直接操作しなくてはならない。

WebDriver とのデータのやり取りは JSON 形式で行われるので,JavaScript で組むのが良いだろう。

2. 課題

2.1 WebDriver とのやり取り

WebDriver とのやり取りには MSXML2.XMLHTTP オブジェクトを用いる。データ送信はよく使うので共通部品を用意した。

初期化部分
//------------------------------------------------------------------------------
// HTTP クライアントの生成
//------------------------------------------------------------------------------
var	client = WScript.CreateObject("MSXML2.XMLHTTP");
//------------------------------------------------------------------------------
// データ送信
//------------------------------------------------------------------------------
var	send = function(method, addr, str) {
	client.open(method, addr, false);
	client.setRequestHeader("Content-Type", "application/json");
	client.setRequestHeader("Pragma", "no-cache");
	client.setRequestHeader("Cache-Control", "no-cache");
	client.setRequestHeader("If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT");
	client.send(str);
};
//------------------------------------------------------------------------------
// セッション ID の取得
//------------------------------------------------------------------------------
send("POST", "http://localhost:9515/session", JSON_stringify({"capabilities":{}}));
var	e = JSON_parse(client.responseText);
var	id = e.value.sessionId;

2.2 IE6 では JSON が使えない

IE6 では JSON.stringify も JSON.parse も使えないのでサブセット(代替品)を自作した。

JSON.parse の代替品
var	JSON_parse = function(str) {
	var e = null; e = eval("e = " + str + ";");
	return e;
};
JSON.stringify の代替品
var	JSON_stringify = function(val) {
	if(val == null) {
		return "null";
	} else if(typeof(val) == "number" || typeof(val) == "boolean") {
		return val.toString();
	} else if(typeof(val) == "string") {
		return "\"" + val + "\"";
	} else if(typeof(val) == "object") {
		if(val.length == undefined) {
			var	a = [];
			for(var i in val)
				a.push("\"" + i + "\":" + JSON_stringify(val[i]));
			return "{" + a.join(",") + "}";
		} else {
			var	a = [];
			for(var i = 0; i < val.length; i++)
				a.push( JSON_stringify(val[i]));
			return "[" + a.join(",") + "]";
		}
	}
};

2.3 BASE64 デコードとバイナリ形式での保存

スクリーンショットは PNG 形式で格納され,それを BASE64 エンコードしたテキストとして受け取るようだ。BASE64 デコードは MSXML2.DOMDocument を使う。またバイナリ形式でのファイル保存には ADODB.Stream を使う。

初期化部分
//------------------------------------------------------------------------------
// ADODB.Stream オブジェクトの生成
//------------------------------------------------------------------------------
var	stream = WScript.CreateObject("ADODB.Stream")
//------------------------------------------------------------------------------
// MSXML2.DOMDocument オブジェクトの生成
//------------------------------------------------------------------------------
var	doc = WScript.CreateObject("MSXML2.DOMDocument");
var	element = doc.createElement("base64");
スクリーンショットの保存部分
send("GET", "http://localhost:9515/session/" + id + "/screenshot", "");
var	obj = JSON_parse(client.responseText);
element.dataType = "bin.base64";
element.text = obj.value;
stream.Type = 1;					// adTypeBinary = 1
stream.Open();
stream.Write(element.nodeTypedValue)
stream.SaveToFile(filename, 2);		// adSaveCreateOverWrite = 2
stream.Close();

3. 実装コード

ささっと作ったつもりだったが,エラー処理を入れたら結構長くなってしまった・・・

msEdgeCap.js
// メイン関数
function main() {
	// JSON 文字列の解析
	var	JSON_parse = function(str) {
		var e = null; e = eval("e = " + str + ";");
		return e;
	};
	// JSON オブジェクトの文字列化
	var	JSON_stringify = function(val) {
		if(val == null) {
			return "null";
		} else if(typeof(val) == "number" || typeof(val) == "boolean") {
			return val.toString();
		} else if(typeof(val) == "string") {
			return "\"" + val + "\"";
		} else if(typeof(val) == "object") {
			if(val.length == undefined) {
				var	a = [];
				for(var i in val)
					a.push("\"" + i + "\":" + JSON_stringify(val[i]));
				return "{" + a.join(",") + "}";
			} else {
				var	a = [];
				for(var i = 0; i < val.length; i++)
					a.push( JSON_stringify(val[i]));
				return "[" + a.join(",") + "]";
			}
		}
	};
	// ウェブドライバ名
	var	driver_name = "msedgedriver.exe";
	// WScript.Shell オブジェクトの生成
	var	shell = WScript.CreateObject("WScript.Shell");
	// 既にウェブ・ドライバが起動されている場合はそのまま利用する
	// そうでない場合、新しいウィンドウを開いてウェブ・ドライバを起動する
	(function(){
		var	locator = WScript.CreateObject("WbemScripting.SWbemLocator");
		var	service = locator.ConnectServer();
		var	list = service.ExecQuery("Select * From Win32_Process Where Name = '" + driver_name + "'");
		if(list.Count == 0) shell.Run(driver_name);
	})();
	// ADODB.Stream オブジェクトの生成
	var	stream = WScript.CreateObject("ADODB.Stream")
	// MSXML2.DOMDocument オブジェクトの生成
	var	element = (function(){
		var	doc = WScript.CreateObject("MSXML2.DOMDocument");
		return doc.createElement("base64");
	})();
	// HTTP クライアントの生成
	var	client = WScript.CreateObject("MSXML2.XMLHTTP");
	var	send = function(method, addr, str) {
		client.open(method, addr, false);
		client.setRequestHeader("Content-Type", "application/json");
		client.setRequestHeader("Pragma", "no-cache");
		client.setRequestHeader("Cache-Control", "no-cache");
		client.setRequestHeader("If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT");
		client.send(str);
	};
	// セッションIDの取得
	send("POST", "http://localhost:9515/session", JSON_stringify({"capabilities":{}}));
	if(client.Status != 200) {
		WScript.StdErr.WriteLine("セッションIDの取得に失敗しました!!");
		return -1;
	}
	var	e = JSON_parse(client.responseText);
	if(!e.value || !e.value.sessionId) {
		WScript.StdErr.WriteLine("セッションIDの取得に失敗しました!!");
		return -1;
	}
	var	id = e.value.sessionId;
	if(e.value && e.value.capabilities) {
		var	f = e.value.capabilities;
		if(f.browserName && f.browserVersion)
			WScript.StdErr.WriteLine(f.browserName + ":" + f.browserVersion);
		if(f.msedge && f.msedge.msedgedriverVersion)
			WScript.StdErr.WriteLine(driver_name + ":" + f.msedge.msedgedriverVersion);
	}
	// スクリーンショットをとる
	var	count = 0;
	for(;;) {
		// キー入力
		WScript.StdErr.Write("SCREEN SHOT(ENTER)/WINDOW LIST(W)/WINDOW SELECT(0-9)/QUIT(Q):");
		var	key = WScript.StdIn.ReadLine();
		if(key == "Q" || key == "q") {
			// 終了
			WScript.StdErr.WriteLine("終了しました。");
			break;
		} else if(key == "W" || key == "w") {
			// ウィンドウリストを表示する
			if(!(function(){
				send("GET", "http://localhost:9515/session/" + id + "/window/handles", "");
				if(client.Status != 200) {
					WScript.StdErr.WriteLine("ウィンドウハンドルの取得に失敗しました!!");
					return false;
				}
				var	obj = JSON_parse(client.responseText);
				if(!obj.value || !obj.value.length) {
					WScript.StdErr.WriteLine("ウィンドウハンドルの取得に失敗しました!!");
					return false;
				}
				for(var i = 0; i < obj.value.length; i++)
					WScript.StdErr.WriteLine("[" + i + "] " + obj.value[i]);
				return true;
			})() ) break;
		} else if(/^[0-9]+$/.test(key)) {
			// ウィンドウを選択する
			if(!(function(){
				send("GET", "http://localhost:9515/session/" + id + "/window/handles", "");
				if(client.Status != 200) {
					WScript.StdErr.WriteLine("ウィンドウハンドルの取得に失敗しました!!");
					return false;
				}
				var	obj = JSON_parse(client.responseText);
				if(!obj.value || !obj.value.length) {
					WScript.StdErr.WriteLine("ウィンドウハンドルの取得に失敗しました!!");
					return false;
				}
				var	n = parseInt(key);
				if(n < 0 || n >= obj.value.length) {
					WScript.StdErr.WriteLine("不正なウィンドウを指定しました!!");
					return true;
				}
				send("POST", "http://localhost:9515/session/" + id + "/window",
					JSON_stringify({ "handle": obj.value[n] }));
				if(client.Status != 200) {
					WScript.StdErr.WriteLine("ウィンドウの選択に失敗しました!!");
					return false;
				}
				WScript.StdErr.WriteLine("ウィンドウ [" + n + "] を選択しました。");
				return true;
			})()) break;
		} else if(key == "") {
			// スクリーンショットを取得する
			if(!(function(){
				send("GET", "http://localhost:9515/session/" + id + "/screenshot", ""); 
				if(client.Status != 200) {
					WScript.StdErr.WriteLine("スクリーンショットの取得に失敗しました!!");
					return false;
				}
				var	obj = JSON_parse(client.responseText);
				if(!obj.value || obj.value.length == 0 || obj.value.length % 4 != 0) {
					WScript.StdErr.WriteLine("スクリーンショットの取得に失敗しました!!");
					return false;
				}
				// BASE64 デコードして保存する
				var	filename = ("0000" + count).slice(-4) + ".PNG";
				element.dataType = "bin.base64";
				element.text = obj.value;
				stream.Type = 1;					// adTypeBinary = 1
				stream.Open();
				stream.Write(element.nodeTypedValue)
				stream.SaveToFile(filename, 2);		// adSaveCreateOverWrite = 2
				stream.Close();
				WScript.StdErr.WriteLine(filename + " ... 保存しました。")
				count++;
				return true;
			})()) break;
		} else {
			WScript.StdErr.WriteLine(key + " は不正なコマンドです!!");
		}
	}
	// ウェブ・ブラウザを閉じる
	send("DELETE", "http://localhost:9515/session/" + id, "");
	return 0;
}
// メイン関数の呼び出し
var	ret = main(WScript.Arguments.Unnamed);
WScript.Quit(ret);

4. 実行画面

コマンドラインから起動すると Edge と WebDriver のバージョンを表示する。ENTER キーを押すとスクリーンショットを取る。ファイル名は 0000.PNG, 0001.PNG, 0002.PNG ... と四桁の連番とする。ウィンドウ(タブ)が複数ある場合のため,ウィンドウ(タブ)の一覧機能と選択機能を追加した。

msedge:119.0.2151.93
msedgedriver.exe:119.0.2151.44 (44557a29097af522913330e5b41f74810bdfb1a2)
SCREEN SHOT(ENTER)/WINDOW LIST(W)/WINDOW SELECT(0-9)/QUIT(Q):

5. 余談

ちなみに職場の同僚に尋ねたら,ブラウザを二つ開いてスライド見ながらテストを解いているとのこと。

6. 参考資料

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3