【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処理を待機させています。
あとはサブルーチン処理(後半)を埋めれば、動作します。
コード(後半)はこちら。
ここから先は
¥ 1,500
この記事が気に入ったらサポートをしてみませんか?