【VBA】セレニウムなし、WebDriverなし、VBA-JSONなしでクローム操作にチャレンジしてみた①

<h2>きっかけ

半年～1年前に会社で「Selenium」と「WebDriver」の使用申請をしたところ却下されました。


しばらく、放置していたのですが、IEのサポート終了が迫ってきたこともあり、
なんかいい方法ないかなーとおもってネットで探していたら、以下の記事・・・

https://www.ka-net.org/blog/?p=14131

・・・をみつけ、記事の中でEdge操作ツール(Selenium,WebDriver不使用)が紹介されていました。
<br>

<br>ここで初めて **ChromeDevToolsProtocol** （以下CDP）の存在を知り、それを使えば

<h3>Selenium,WebDriverなしでクロームが操作できる！！<h3>

ということを知りました。

記事で紹介されているツールを会社で使用申請しても、却下されるのは目に見えているため、
「CDPについて調べて自分で作ってみよう！」と思ったのが、チャレンジのきっかけです。
<br>

:::note warn
<h4>＜補足＞

※記事で紹介されているEdge操作ツール自体は、Edgeとの通信にパイプ処理が使われており、
個人的にはまだパイプについて詳しく学習したことがなく
「パイプ？？？」
というレベルだったので、レベルが高すぎて理解できず、全然参考に出来ませんでした・・・

そのため、今回はCDP通信する際にスタンダードだと思われるWebSoketを使用しての実装にチャレンジしました。

また、現在完成している機能としては、「クロームとの通信開始」「URLを開く」「タブを閉じる」程度の状況です。

そのためこの記事の内容としては、ChromeとWebSocketでCDP通信を行い、指定のURLを開き、タブを閉じるところまでの方法をご紹介する内容になります。
:::

<h4>※最終的にはライブラリっぽいものを作りました。(2020/8)<br>
実装の詳細よりも使えるものに興味がある方はこちらへ

https://qiita.com/kabkabkab/items/9952a796ee9244fc98ad#comment-71accef535e16182ac19

<h2>概要

***以下の手順でChromeを操作することができました。***
1.  Chrome起動時に「--remote-debugging-port=9222」オプションをつけて起動する。
⇒ローカル内でChromeがリモートホストとして起動する。

2. 「localhost:9222/json」(1.で起動したクローム)に対して、GetメソッドでHTTP通信を行う。
⇒開いているタブに関する情報がJSON形式のレスポンスとして返ってくる。
※そのJSONの中に「WebSocketDebuggerURL」という、CDPでChromeに指示を与えるために
　 必要な通信先や「ID」というブラウザ内の操作対象を指定する項目が含まれています。

3. 「WebSocketDebuggerURL」にまずHTTP通信を行う。

3.  HTTP通信からWebSocket通信にUpGradeする。
    ※CDPでのやりとりにはWebSocket通信が必要なため

4. CDPで定義されている「Method」をJSONのオブジェクト形式にして、WebSocket通信で投げる。



<h2>実装概要

***以下のように実装を行っております。***

1. WSHオブジェクトを使用して、クローム起動

2. XMLHttpオブジェクトを使用して「localhost:9222/json」Http通信(Get)を行う
   VBAの文字列操作関数を使用して、レスポンス（JSON）から必要情報取得

3. WindowsAPI(WinHttp関連のAPI)を使用して「WebSocketDebuggerURL」にHttp通信を行う。

4. WindowsAPI(WinHttp関連のAPI)を使用して、HTTP通信をWebSockt通信にUpGradeさせる。

5. WindowsAPI(WebSocket関連のAPI)を使用して、CDPメソッドの送信、レスポンスの受信を行う。


<h2>実装詳細

<h3>※注意※

<h5>
コードは本来、全てクラスで実装しますが、わかりやすさのため、<br>
以下のような記述をしています。<br>

<h6>

- 該当箇所のみを標準モジュールに記述したような形式でコード例を記載
- リテラルは本来は定数宣言しますが、そのままリテラルで記載
- グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
（クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい）
<br>

***＜実装１.WSHオブジェクトを使用して、クローム起動＞***

~~~vb:vba
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
~~~

これはそのまんまですね。クロームをオプション付きで起動しているだけです。
結果：ローカル内でクロームがリモートサーバーとして起動します。
<br><br>

***＜実装２.XMLHttpオブジェクトを使用して「localhost:9222/json」にHttp通信(Get)を行いJSON取得＞***
     

~~~vb:vba
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のイメージ（青線箇所＝項目名、赤線箇所＝取得したい情報）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1804114/84d72e10-3394-cb01-7c20-9bc31cb2f8f2.png)

※ちなみに「/version」というパスにGetすると、ブラウザ自体の情報が取得できたりします。
<br>


***＜実装２.の続き：VBAの文字列操作関数を使用して、JSONから必要情報取得＞***
~~~vb:vba
Public resourcePath_ As String
Public targetID_ As String

Private Sub SetTragetInfo(JSON As String)
    '１WebDebuggingURLの取得
    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), """", "")
    
    '２後々必要になるので、WebDebuggingURLのパス部分のみ別途取得
    resourcePath_ = Mid(WebDebuggingURL , 20)
    
    '３後々必要になるので、更にresourcePath_から取得
    targetID_ = Replace(resourcePath_, "/devtools/page/", "")
End Sub
~~~~
今回VBA-JSONは使わない前提なので、JSON関連の文字列処理は後回しにしており、いったん先に進む目的でこんなゴミコードで済ませてます。

一旦コードのゴミさ加減には目をつぶって、このステップでは後々必要になる「リソースパス」と「操作対象を示すID情報」を取得しておきたいんだなという趣旨をご理解ください。

ちなみにWebDebuggerUrlは構成として、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1804114/2fc0b26b-040c-0445-929a-2ad7dff222c4.png)
赤線部＝ループバックアドレス
青線部＝リソースパス
オレンジ線部＝ターゲットID（操作対象を示すID情報）
となっており、欲しいのはリソースパスとターゲットIDだけです。
<br><br>


***次の実装からWinAPIを使用し難易度が上がるのですが、そのせいか記事が長くなってきており、続けて記載すると記事全体がとても長くなってしまうので、いったんここで切ります。***
<br>
***続きはこちらです。***

https://qiita.com/kabkabkab/items/e4243694cfc474c4c950