きっかけ
半年~1年前に会社で「Selenium」と「WebDriver」の使用申請をしたところ却下されました。
しばらく、放置していたのですが、IEのサポート終了が迫ってきたこともあり、
なんかいい方法ないかなーとおもってネットで探していたら、以下の記事・・・
・・・をみつけ、記事の中でEdge操作ツール(Selenium,WebDriver不使用)が紹介されていました。
ここで初めて ChromeDevToolsProtocol (以下CDP)の存在を知り、それを使えば
Selenium,WebDriverなしでクロームが操作できる!!
ということを知りました。
記事で紹介されているツールを会社で使用申請しても、却下されるのは目に見えているため、
「CDPについて調べて自分で作ってみよう!」と思ったのが、チャレンジのきっかけです。
<補足>
※記事で紹介されているEdge操作ツール自体は、Edgeとの通信にパイプ処理が使われており、
個人的にはまだパイプについて詳しく学習したことがなく
「パイプ???」
というレベルだったので、レベルが高すぎて理解できず、全然参考に出来ませんでした・・・
そのため、今回はCDP通信する際にスタンダードだと思われるWebSoketを使用しての実装にチャレンジしました。
また、現在完成している機能としては、「クロームとの通信開始」「URLを開く」「タブを閉じる」程度の状況です。
そのためこの記事の内容としては、ChromeとWebSocketでCDP通信を行い、指定のURLを開き、タブを閉じるところまでの方法をご紹介する内容になります。
※最終的にはライブラリっぽいものを作りました。(2020/8)
実装の詳細よりも使えるものに興味がある方はこちらへ
概要
以下の手順でChromeを操作することができました。
-
Chrome起動時に「--remote-debugging-port=9222」オプションをつけて起動する。
⇒ローカル内でChromeがリモートホストとして起動する。 -
「localhost:9222/json」(1.で起動したクローム)に対して、GetメソッドでHTTP通信を行う。
⇒開いているタブに関する情報がJSON形式のレスポンスとして返ってくる。
※そのJSONの中に「WebSocketDebuggerURL」という、CDPでChromeに指示を与えるために
必要な通信先や「ID」というブラウザ内の操作対象を指定する項目が含まれています。 -
「WebSocketDebuggerURL」にまずHTTP通信を行う。
-
HTTP通信からWebSocket通信にUpGradeする。
※CDPでのやりとりにはWebSocket通信が必要なため -
CDPで定義されている「Method」をJSONのオブジェクト形式にして、WebSocket通信で投げる。
実装概要
以下のように実装を行っております。
-
WSHオブジェクトを使用して、クローム起動
-
XMLHttpオブジェクトを使用して「localhost:9222/json」Http通信(Get)を行う
VBAの文字列操作関数を使用して、レスポンス(JSON)から必要情報取得 -
WindowsAPI(WinHttp関連のAPI)を使用して「WebSocketDebuggerURL」にHttp通信を行う。
-
WindowsAPI(WinHttp関連のAPI)を使用して、HTTP通信をWebSockt通信にUpGradeさせる。
-
WindowsAPI(WebSocket関連のAPI)を使用して、CDPメソッドの送信、レスポンスの受信を行う。
実装詳細
※注意※
コードは本来、全てクラスで実装しますが、わかりやすさのため、
以下のような記述をしています。
- 該当箇所のみを標準モジュールに記述したような形式でコード例を記載
- リテラルは本来は定数宣言しますが、そのままリテラルで記載
- グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
コードは本来、全てクラスで実装しますが、わかりやすさのため、
以下のような記述をしています。
- 該当箇所のみを標準モジュールに記述したような形式でコード例を記載
- リテラルは本来は定数宣言しますが、そのままリテラルで記載
- グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
- 該当箇所のみを標準モジュールに記述したような形式でコード例を記載
- リテラルは本来は定数宣言しますが、そのままリテラルで記載
- グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
<実装1.WSHオブジェクトを使用して、クローム起動>
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr)
Private Sub StartChrome()
CreateObject("WScript.Shell").Run _
"""chrome.exe"" --remote-debugging-Port=9222", 1, False
Sleep 2000
End Sub
これはそのまんまですね。クロームをオプション付きで起動しているだけです。
結果:ローカル内でクロームがリモートサーバーとして起動します。
<実装2.XMLHttpオブジェクトを使用して「localhost:9222/json」にHttp通信(Get)を行いJSON取得>
Private Function HTTPGetToChromeDebuggingPort() As String
Dim client As Object
Set client = CreateObject("MSXML2.ServerXMLHTTP")
client.Open "GET", "http://127.0.0.1:9222/json"
client.SetRequestHeader "Content-Type", "application/json"
client.Send
HTTPGetToChromeDebuggingPort = client.responsetext
End Function
これもそのままです。
1.で起動したクロームのデバッグポート+「/json」というURLに、XMLHTTPでGet通信してるだけです。
今回のコード例のように「/json」というパスに対してGetすると、クローム側からはレスポンスとして、開いているタブに関する情報が、以下のようなJSON形式で返ります。
参考:戻り値のJSONのイメージ(青線箇所=項目名、赤線箇所=取得したい情報)
※ちなみに「/version」というパスにGetすると、ブラウザ自体の情報が取得できたりします。
<実装2.の続き:VBAの文字列操作関数を使用して、JSONから必要情報取得>
Public resourcePath_ As String
Public targetID_ As String
Private Sub SetTragetInfo(JSON As String)
'1WebDebuggingURLの取得
Dim startNum As Long, URLLength As Long
startNum = InStr(JSON, "ws://")
URLLength = InStr(JSON, "}") - startNum - 2
Dim WebDebuggingURL As String
WebDebuggingURL = Replace(Mid(JSON, startNum, URLLength), """", "")
'2後々必要になるので、WebDebuggingURLのパス部分のみ別途取得
resourcePath_ = Mid(WebDebuggingURL , 20)
'3後々必要になるので、更にresourcePath_から取得
targetID_ = Replace(resourcePath_, "/devtools/page/", "")
End Sub
今回VBA-JSONは使わない前提なので、JSON関連の文字列処理は後回しにしており、いったん先に進む目的でこんなゴミコードで済ませてます。
一旦コードのゴミさ加減には目をつぶって、このステップでは後々必要になる「リソースパス」と「操作対象を示すID情報」を取得しておきたいんだなという趣旨をご理解ください。
ちなみにWebDebuggerUrlは構成として、
赤線部=ループバックアドレス
青線部=リソースパス
オレンジ線部=ターゲットID(操作対象を示すID情報)
となっており、欲しいのはリソースパスとターゲットIDだけです。
次の実装からWinAPIを使用し難易度が上がるのですが、そのせいか記事が長くなってきており、続けて記載すると記事全体がとても長くなってしまうので、いったんここで切ります。
続きはこちらです。