はじめに
Excelでwebブラウザの操作を自動化する最小機能のみ実装したフレームワークの「TinySeleniumVBA」があります。
これは今のところActiveXを使用しているため、Windows専用になっています。ActiveXの部分を取り除き代替部分をVBA-toolsライブラリー群を使えば、Mac上のExcelで動作するかもと取り組みました。
VBA-toolsを用いてWindows上では動作するようになったのですが、残念ながらMacではエラーで動作しませんでした。
調査した結果、WebDriver(chromedriver or msedgedriver)のプロセスが起動していないことが分かりました。
Shell関数の戻り値 0 にはなっていないため、エラーは出ていません。
' Start WebDriver executable
If Shell(driverPath, vbMinimizedNoFocus) = 0 Then
MsgBox "Failed in starting WebDriver"
End
End If
環境
- Mac Book Pro(2.3 GHz 8コアIntel Core i9) Monterey 12.2.1
- Microsoft Office Home and Student 2019
MacのShell関数は使えない
Macintoshコンピュータでは、MacID関数を使用して、アプリケーションの名前の代わりにアプリケーションの署名を指定できます。次の例では、Microsoft Wordの署名を使用しています。Shell MacID("MSWD")
https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/shell-function
MacIDって検索してみたけど、よく分からない。他の方法を模索する。
Shellの代替案
MacScript関数を利用する方法
ネットで検索するとShell関数の代わりにMacScript関数を使用する方法が散見される。
しかし、MacScript関数は廃止され、サポートされなくなりました。
MacScript について
以前のバージョンの Office for Mac には、インラインの Applescript をサポートしている MacScript と呼ばれるコマンドが実装されていました。 このコマンドは、Office 2016 for Mac でもまだ存在していますが、MacScript は使用されなくなりました。 サンドボックス制限のため、MacScript コマンドは、Finder などのほかのアプリケーションを Office 2016 for Mac で呼び出すことができません。 Office 2016 for Mac のアプリでは、MacScript コマンドの代わりに AppleScriptTask コマンドを使用することをお勧めします。
https://docs.microsoft.com/ja-jp/office/vba/office-mac/applescripttask
エラーにはならないが、常にFALSEが返ってくるとのこと。
Excel2011ではVBA内にAppleScriptをインラインで記述し、MacScript関数で実行する事ができましたが、Excel2016以降、セキュリティ上の観点から廃止されました。Excel2019で実行するとエラーにはなりませんが、常にFALSEが帰ってきます。
Excel 2019 for Mac OS判定とAppleScriptTask利用方法
AppleScriptTask関数を利用する方法
Office 2016 for Mac 以降では、MacScript関数の代わりに AppleScriptTask関数を使用する。
先述したTinySeleniumVBAのShell関数は、AppleScriptTask関数に置き換えることで対応できた。
AppleScriptTask関数を利用する手順は2段階となります。
- スクリプトファイルを「~/Library/Application Scripts/com.microsoft.Excel/」に保存する。
- VBA内でAppleScriptTaskからスクリプトファイルを呼び出す。
VBAではライブラリーフォルダにアクセスできないので配布するとなると少し面倒です。
最初、Shellが同期した状態で動いたため次のプロセスを終了するまで先に進まなかったのですが、下記サイトの方法で「" &"」を付けることで非同期で動かすことが出来ました。
- do shell script without waiting
- (Linux) &を付けてコマンドをバックグラウンド実行する
- いい加減覚えよう。 command > /dev/null 2>&1の意味
【2022/05/05追記】
出力結果を捨ててしまうため、"> /dev/null 2>&1"を付けるのをやめました。 2022/05/21 非同期のみ付け直しました。
shell.scptとして、同期と非同期のハンドラ(関数)に分けました。
on synchandler(param)
try
set ret to do shell script param
return ret
on error number n
return "error " & n
end try
end synchandler
on ansynchandler(param)
try
set ret to do shell script param & "> /dev/null 2>&1 &"
return ret
on error number n
return "error " & n
end try
end ansynchandler
Public Sub main()
' Start WebDriver
Driver.Chrome "/usr/local/bin/chromedriver"
' Start WebDriver
Public Sub Start(ByVal driverPath As String, ByVal driverUrl As String, Optional ByVal driverName As String = vbNullString)
'Start WebDriver executable
#If Mac Then
Dim scriptResult As String
scriptResult = AppleScriptTask("shell.scpt", "ansynchandler", driverPath)
#Else
If Shell(driverPath, vbMinimizedNoFocus) = 0 Then
MsgBox "Failed in starting WebDriver"
End If
#End If
実行することで、chromedriverが起動していることが分かります。
yaju@hi ~ % ps aux | grep chromedriver
yaju 12435 0.0 0.0 34132100 916 s001 S+ 7:39PM 0:00.00 grep chromedriver
yaju 11642 0.0 0.0 67343788 4852 ?? S 7:33PM 0:00.02 /usr/local/bin/chromedriver
C ライブラリの popen 関数を利用する方法
VBA-WEBライブラリーの中にも、curlを使用するためにExecuteInShell
関数が用意されています。
- Excel for Mac (VBA) から HTTP GET する方法
- How do I issue an HTTP GET from Excel VBA for Mac - stackoverflow
#If Mac Then
#If VBA7 Then
Private Declare PtrSafe Function web_popen Lib "/usr/lib/libc.dylib" Alias "popen" (ByVal web_Command As String, ByVal web_Mode As String) As LongPtr
Private Declare PtrSafe Function web_pclose Lib "/usr/lib/libc.dylib" Alias "pclose" (ByVal web_File As LongPtr) As LongPtr
Private Declare PtrSafe Function web_fread Lib "/usr/lib/libc.dylib" Alias "fread" (ByVal web_OutStr As String, ByVal web_Size As LongPtr, ByVal web_Items As LongPtr, ByVal web_Stream As LongPtr) As LongPtr
Private Declare PtrSafe Function web_feof Lib "/usr/lib/libc.dylib" Alias "feof" (ByVal web_File As LongPtr) As LongPtr
#Else
Private Declare Function web_popen Lib "libc.dylib" Alias "popen" (ByVal web_Command As String, ByVal web_Mode As String) As Long
Private Declare Function web_pclose Lib "libc.dylib" Alias "pclose" (ByVal web_File As Long) As Long
Private Declare Function web_fread Lib "libc.dylib" Alias "fread" (ByVal web_OutStr As String, ByVal web_Size As Long, ByVal web_Items As Long, ByVal web_Stream As Long) As Long
Private Declare Function web_feof Lib "libc.dylib" Alias "feof" (ByVal web_File As Long) As Long
#End If
#End If
'
' Execute the given command
'
' @internal
' @method ExecuteInShell
' @param {String} Command
' @return {ShellResult}
''
Public Function ExecuteInShell(web_Command As String) As ShellResult
#If Mac Then
#If VBA7 Then
Dim web_File As LongPtr
#Else
Dim web_File As Long
#End If
Dim web_Chunk As String
Dim web_Read As Long
On Error GoTo web_Cleanup
web_File = web_popen(web_Command, "r")
If web_File = 0 Then
' TODO Investigate why this could happen and what should be done if it happens
Exit Function
End If
Do While web_feof(web_File) = 0
web_Chunk = VBA.Space$(50)
web_Read = CLng(web_fread(web_Chunk, 1, Len(web_Chunk) - 1, web_File))
If web_Read > 0 Then
web_Chunk = VBA.Left$(web_Chunk, web_Read)
ExecuteInShell.Output = ExecuteInShell.Output & web_Chunk
End If
Loop
web_Cleanup:
ExecuteInShell.ExitCode = CLng(web_pclose(web_File))
#End If
End Function
この方法で/usr/local/bin/chromedriver
を起動しようとしたが出来ませんでした。
しかし、lsやcurlやPerlなどのコマンドはExecuteInShell
関数で動作します。
Perlが動くならとターミナル上でPerlのワンライナーでchromedriver
が起動できるのを確認後、VBA側で同じようにchromedriver
を起動させようと下記プログラムを試しました。しかし、エラーにならず処理は通るもののchromedriver
は起動されませんでした。
web_Command = "perl -e 'system(""/usr/local/bin/chromedriver &"")'"
scriptResult = WebHelpers.ExecuteInShell(web_Command)
lsやcurlやPerlなどが格納されている/usr/binフォルダにあるものはセキュリティ上は動作可能になっているのかも知れませんね。
最後に
配布に問題があるものの、chromedriver
などの外部プロセスを動かすには、AppleScriptTask コマンドを使用しAppleScript スクリプト側で動作させるしかなさそうです。