見出し画像

【VBA】Edge標準モードを自動操作(インストール不要)

Edgeの標準モード(いつも見慣れたモダンブラウザ)をVBAで操作するコードを紹介します。

以前EdgeのIEモードを操作する記事を書きましたが、今回は標準モードを操作します。
IEモードでは上手く扱えなかったWebページや動的処理も制御可能になり、汎用性・安定性がUPしました。
※とはいえ、業務都合でIEモードを使うシチュエーションもまだまだ有るでしょうから、以前の記事と上手く使い分けてください。

追加インストールやDL不要(SeleniumやWebDriver不使用)。
ExcelとEdgeとWindowsパソコンがあれば使えます。

全体像はこちら。(よく分からない人は読み飛ばしてOK)
 ①リモートデバッグポートを使ってEdge起動。
 ②Devtools Protocolを介して、アタッチ可能な対象一覧を取得。
 ③操作対象のIDを取得。
 ④ソケット生成&接続(HTTP通信からWebSocket通信へ切替)
 ⑤WebSocket通信を確立。
 ⑥WebSocket通信用のフレーム(コマンド)を生成。
 ⑦WebSocket通信でフレームを送信。
 ⑧送信コマンドに応じてブラウザが動作。
【動作確認済み環境】
・Windows Server 2016 Standard (64bit)、Excel® for Microsoft 365 (64bit)
・Windows 10 Pro (64bit)、Excel® for Microsoft 365 (64bit)
・Windows 11 Home (64bit)、Excel® 2021 (32bit)



Edgeブラウザに標準搭載されている開発者ツール(Chrome Devtools Protocol、略してCDP)機能を使って、ブラウザ操作を実現します。
※具体的なCDPの使い方(活用例)はこちらの記事に纏めました。
 自分の備忘録も兼ねていますが、実装時の参考になれば嬉しいです。

VBAコード

コード(前半)はこちら。

'WSA関連
Private Type WSAData 'ソケット初期化用構造体
 wVersion As Integer
 wHighVersion As Integer
 szDescription As String * 257 'WSA_DESCRIPTIONLEN + 1
 szSystemStatus As String * 129 'WSA_SYS_STATUS_LEN + 1
 iMaxSockets As Integer
 iMaxUdpDg As Integer
 lpVendorInfo As Long
End Type
Private Declare PtrSafe Function WSAStartup Lib "ws2_32.dll" (ByVal wVersionRequired As Integer, ByRef lpWSAData As WSAData) As Long
Private Declare PtrSafe Function WSACleanup Lib "ws2_32.dll" () As Long
Private Declare PtrSafe Function WSAGetLastError Lib "ws2_32.dll" () As Long
'------------------------
'アドレス取得
Private Declare PtrSafe Function inet_addr Lib "ws2_32.dll" (ByVal cp As String) As Long
Private Declare PtrSafe Function htons Lib "ws2_32.dll" (ByVal hostshort As Long) As Integer
'------------------------
'ソケット接続
Private Type sockaddr_in 'ソケットアドレス
 sin_family As Integer
 sin_port As Integer
 sin_addr As Long
 sin_zero1 As Long
 sin_zero2 As Long
End Type
Private Declare PtrSafe Function socket Lib "ws2_32.dll" (ByVal af As Long, ByVal socktype As Long, ByVal protocol As Long) As Long
Private Declare PtrSafe Function closesocket Lib "ws2_32.dll" (ByVal s As Long) As Long
Private Declare PtrSafe Function connect Lib "ws2_32.dll" (ByVal s As Long, ByRef name As sockaddr_in, ByVal namelen As Long) As Long
Private Declare PtrSafe Function ioctlsocket Lib "ws2_32.dll" (ByVal s As Long, ByVal cmd As Long, argp As LongPtr) As Long
Private Const FIONBIO = &H8004667E
'------------------------
'ソケット送受信
Private Declare PtrSafe Function send Lib "ws2_32.dll" (ByVal s As Long, ByRef buf As Any, ByVal length As Long, ByVal flags As Long) As Long
Private Declare PtrSafe Function strsend Lib "ws2_32.dll" Alias "send" (ByVal s As Long, ByVal buf As String, ByVal length As Long, ByVal flags As Long) As Long
Private Declare PtrSafe Function recv Lib "ws2_32.dll" (ByVal s As Long, ByRef buf As Any, ByVal length As Long, ByVal flags As Long) As Long
Private Declare PtrSafe Function strrecv Lib "ws2_32.dll" Alias "recv" (ByVal s As Long, ByVal buf As String, ByVal length As Long, ByVal flags As Long) As Long
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
'モジュールレベル変数
Private Frame() As Byte


'-----------メイン処理-----------
Private Sub Main()
 'Edgeを強制終了(バックグラウンドも含めてプロセスキル)
 Dim ans As Integer
 ans = MsgBox("Edgeを再起動しますが、よろしいですか?", vbOKCancel + vbQuestion, "ブラウザ操作を開始します")
 If ans = 2 Then Exit Sub
 With CreateObject("WScript.Shell")
  .Run "taskkill /F /IM msedge.exe", 0, True
 End With

 'リモートデバックポートを使用してEdge起動
 Dim edgePath As String
 Dim rc As Long
 edgePath = Environ("ProgramFiles(x86)") & "\Microsoft\Edge\Application\msedge.exe"
 'edgePath = Environ("ProgramFiles") & "\Microsoft\Edge\Application\msedge.exe"
 'edgePath = Environ("LOCALAPPDATA") & "\Microsoft\msedge.exe"
 rc = Shell(edgePath & " --remote-debugging-port=9222", vbNormalFocus)
 
 'アタッチ可能なターゲット一覧を取得
 Dim url As String
 Dim req As Object
 Dim resText As String
 url = "http://localhost:9222/json/list" 'Devtools Protocolエンドポイント
 Set req = CreateObject("MSXML2.ServerXMLHTTP")
 'Set req = CreateObject("MSXML2.XMLHTTP")
 'Set req = CreateObject("WinHttp.WinHttpRequest")
 req.Open "GET", url, False
 req.setRequestHeader "Content-Type", "application/json"
 req.send
 resText = req.responseText
 Set req = Nothing
 
 'ターゲット一覧から、操作対象のIDを取得(type=pageであるターゲットのid値)
 Dim reg As Object
 Dim IDs() As String
 Dim ID As String
 Dim nEnd As Long
 Dim TrimText As String
 Set reg = CreateObject("VBScript.RegExp")
 With reg
  .Pattern = """id"": ""[^""]+"""
  .IgnoreCase = True '大文字小文字を区別しない
  .Global = True '全体を検索
  ReDim IDs(.Execute(resText).count - 1)
  For i = 0 To .Execute(resText).count - 1
   IDs(i) = Mid(resText, .Execute(resText)(i).firstindex + 8, .Execute(resText)(i).length - 8)
   nEnd = InStr(.Execute(resText)(i).firstindex, resText, "}")
   TrimText = Mid(resText, .Execute(resText)(i).firstindex, nEnd - .Execute(resText)(i).firstindex)
   If InStr(TrimText, """type"": ""page""") > 0 Then
    ID = IDs(i)
   End If
  Next i
 End With
 Set reg = Nothing

  'Googleページへ移動
  Dim CDP_Command As String
  Dim response As String
  CDP_Command = "{""id"":1, " & _
      """method"":""Page.navigate"", " & _
      """params"":{""url"":""https://www.google.co.jp/""}}"
  response = WebSocket_Submit(CDP_Command, ID)  'Sub①
  'Debug.Print "コマンド送信完了、結果=" & response
 
  '検索ワード入力&検索ボタン押下
  Dim jsCode As String
  jsCode = "document.getElementById('APjFqb').value = '地獄の油揚げ';" & _
       "var buttons = document.getElementsByTagName('input');" & _
       "for (var i = 0; i < buttons.length; i++) {" & _
       " if (buttons[i].type === 'submit') {" & _
       "  buttons[i].click();" & _
       "  break;" & _
       " }" & _
       "}"
  CDP_Command = "{""id"":2, " & _
      """method"":""Runtime.evaluate"", " & _
      """params"":{""expression"":""" & jsCode & """}}"
  response = WebSocket_Submit(CDP_Command, ID)  'Sub①
  'Debug.Print "コマンド送信完了、結果=" & response

  'ページ読込み待機
  Dim EventName As String
  CDP_Command = "{""id"":3, " & _
      """method"":""Page.enable""}"
  EventName = "Page.loadEventFired"
  response = WebSocket_Submit(CDP_Command, ID, EventName) 'Sub①
  'If InStr(response, EventName) > 0 Then
 ' Debug.Print EventName & "イベントを正常検知、結果=" & response
 'End If
End Sub
'-----------メイン処理(おわり)-----------


'-----------サブルーチン処理----------
Private Function WebSocket_Submit(ByVal CDP_Command As String, ByVal ID As String, Optional ByVal EventName As String) As String 'Sub①
 ’本記事の後半で紹介。
End Function
'-----------サブルーチン処理(おわり)----------

このサンプルでは、「Googleを開く→検索ボックスに文字入力→検索ボタンを押す→ページ読み込み待機」の操作を自動化してみました。
1回目のコマンド送信(CDPのNavigateコマンド)でGoogleページへ移動し、2回目のコマンド送信(JavaScriptを実行する部分)で検索ボックスに文字入力&検索ボタン押下を再現しています。
※このようにJavaScript形式で書いた命令文を直接ブラウザに送り込むことも出来るので、細かな操作も再現できます。
なお3回目のコマンド送信では、ページ読込み時に発生するイベントを検出するまでVBA処理を待機させています。

あとはサブルーチン処理(後半)を埋めれば、動作します。
コード(後半)はこちら。

ここから先は

36,379字 / 2画像

¥ 1,500

この記事が気に入ったらサポートをしてみませんか?