SeleniumBasic中的IWebDriver對象的ExecuteScript方法用於執行JavaScript腳本。語法如下
Function ExecuteScript(script As String, [arg0], [arg1], [arg2])
后面3個是可選參數。
調用ExecuteScript大體分為兩種情況:無返回值的和有返回值的。
執行無返回值的外部使用Call關鍵字,例如:
Call WD.ExecuteScript("alert('Hello,ryueifu!')")
alert('Hello,ryueifu!')是JavaScript語法,作用是彈出一個對話框,無返回值。所以外側使用VBA的Call
另一種是把JavaScript代碼執行的結果返回給VBA。例如下面的程序借助JavaScript計算數學題,結果返回給prod:
Dim prod As Integer
prod = WD.ExecuteScript("return 7-4*5")
Debug.Print prod
打印結果是-13
注意:要想從ExecuteScript中得到返回值,在腳本中必須有return關鍵字!否則返回Nothing
另外,SeleniumBasic的ExecuteScript函數,除了執行的腳本代碼以外,還可以最多設置3個可選參數。這些參數與腳本代碼中的arguments對號入座。這樣可以增強程序的靈活性。
例如:
prod = WD.ExecuteScript("var p=1;p=arguments[2]-arguments[0]*arguments[1];return p", CStr(4), CStr(5), CStr(7))
Debug.Print prod
上述程序的Cstr(4)對應於arguments[0],以此類推。返回的結果仍然是-13。
當然,同一個參數也可以在腳本中被多次訪問,例如:
Call WD.ExecuteScript("alert(arguments[0]+'就是'+arguments[0])", Application.UserName)
Excel VBA中的用戶名被調用了兩次,顯示在網頁上。
以上是執行JavaScript腳本的基本知識。
下面介紹幾個非常實用的JavaScript技術。
- 利用JavaScript定位元素
通過前面的學習,SeleniumBasic中的FindElement系列方法只能向下查找,也就是說只能查找已知元素包含的子孫元素。而不能得到一個元素的父級、兄弟元素。實際上這個結論是不對的。FindElement系列的8個定位方法,其中根據XPath、CssSelector這兩個定位方法,可以用來定位父級、兄弟元素。
不過本節要介紹的是通過JavaScript代碼來定位,其理論依據請參考
- https://www.w3school.com.cn/jsref/dom_obj_document.asp
- https://www.w3school.com.cn/jsref/dom_obj_all.asp
因為JavaScript可以遍歷和定位DOM中的元素,SeleniumBasic又可以執行JavaScript腳本,因此可以實現。
下面以遍歷百度首頁左上角的7個超鏈接為例講解。在開發工具中看一下HTML的構成。
可以看到這7個超鏈接,位於一個div中。因此,先定位到div,然后找到它的第一個兒子、最后一個兒子,前一個兒子后一個兒子,最后一句parentNode返回其兒子的父親,也就是他本人。
Dim div As SeleniumBasic.IWebElement Dim anchor As IWebElement Set div = WD.ExecuteScript("return document.getElementById('s-top-left')") Set anchor = WD.ExecuteScript("return arguments[0].firstChild", div) Debug.Print anchor.text Set anchor = WD.ExecuteScript("return arguments[0].lastChild", div) Debug.Print anchor.text Set anchor = WD.ExecuteScript("return arguments[0].previousSibling", anchor) Debug.Print anchor.text Set anchor = WD.ExecuteScript("return arguments[0].nextSibling", anchor) Debug.Print anchor.text Set anchor = WD.ExecuteScript("return arguments[0].parentNode", anchor) Debug.Print anchor.text
以上代碼中的5個打印語句的結果如下:
注意領會理解上述代碼的精髓,ExecuteScript方法中的參數arguments[0]接收的是一個IWebElement對象,然后該函數返回的結果是另一個IWebElement。
通過這個實例,可以看出定位元素毫無問題。
- 獲取和修改網頁元素的屬性
SeleniumBasic的IWebElement具有GetAttribute方法,可以返回指定屬性名稱的屬性值。
但是JavaScript中的網頁元素都有getAttribute和setAttribute,既可以返回又可以設置屬性。
眾所周知,百度的搜索按鈕的HTML定義如下:
<input type="submit" id="su" value="百度一下" class="bg s_btn">
在VBA中運行
Set button = WD.FindElementById("su")
Debug.Print WD.ExecuteScript("return arguments[0].getAttribute(arguments[1])", button, "value")
就可以得到它的value屬性:百度一下。
同理,運行
Call WD.ExecuteScript("arguments[0].setAttribute(arguments[1],arguments[2])", button, "value", "百度兩下")
可以修改其屬性,造成按鈕文字被修改。
上面這行代碼用了3個arguments,把ExecuteScript的用法演繹的淋漓盡致。如果不使用參數,純粹的JavaScript代碼是:
Call WD.ExecuteScript("document.getElementById('su').setAttribute('value','百度三下')")
注意:JavaScript中關鍵字和函數名嚴格區分大小寫。
- 調用網頁元素的函數和方法
JavaScript中網頁元素有眾多的函數和方法。
例如下面的程序,自動輸入關鍵字中秋節,自動點擊“百度一下”按鈕。
WD.URL = "https://www.baidu.com"
WD.ExecuteScript "document.getElementById('kw').value='中秋節'"
WD.ExecuteScript "document.getElementById('su').click()"
也可以把程序中的對象作為參數傳遞進去,例如下面的程序,把keyword和button傳遞到腳本中,通過執行腳本就實現了關鍵字的輸入和搜索按鈕的點擊。
WD.URL = "https://www.baidu.com" Dim form As SeleniumBasic.IWebElement Dim keyword As SeleniumBasic.IWebElement Dim button As SeleniumBasic.IWebElement Set form = WD.FindElementById("form") Set keyword = form.FindElementById("kw") Set button = form.FindElementById("su") WD.ExecuteScript "arguments[0].value='SeleniumBasic';arguments[1].submit()", keyword, button
運行到ExecuteScript那句時,瀏覽器的樣子如下:
另外,HTMLDocument中有InnerHTML和OuterHTML這兩個概念,用來返回網頁元素的HTML定義。SeleniumBasic中只提供了IWebElement的TagName、Text等,無法返回完整的HTML標簽定義。
Set button = WD.FindElementById("su")
Debug.Print WD.ExecuteScript("return arguments[0].outerHTML", button)
以上代碼的打印結果是:
<input type="submit" id="su" value="百度三下" class="bg s_btn">
而下面這句則返回按鈕的父級元素的定義
Debug.Print WD.ExecuteScript("return arguments[0].parentNode.outerHTML", button)
結果為:
<span class="bg s_btn_wr"><input type="submit" id="su" value="百度三下" class="bg s_btn"></span>
- 操作DOM文檔對象
JavaScript中的document對象是網頁文檔的最頂級對象。各種成員請參考https://www.w3school.com.cn/jsref/dom_obj_document.asp
下面僅僅舉一個返回網頁當前的Cookie
Debug.Print WD.ExecuteScript("return document.cookie")
總之,JavaScript腳本無處不在,SeleniumBasic中通過ExecuteScript方法既可以把VBA的數據提交到網頁,也可以把網頁信息獲取到VBA中。
- 追記:獲取復數個網頁元素
JavaScript中的document對象以及網頁元素對象,具有getElementsByTagName等方法(注意復數s),可以返回具有多個元素的集合。
百度首頁的form元素的HTML定義如下
可以看到form下面包含很多input標簽。下面程序中的ExecuteScript函數返回一個IWebElement數組。在循環中打印每個input的HTML定義。
Set form = WD.FindElementById("form") Dim Elements() As IWebElement Elements = WD.ExecuteScript("return arguments[0].getElementsByTagName('input')", form) Erase Elements Elements = form.FindElementsByTagName("input") For i = 0 To UBound(Elements) Debug.Print WD.ExecuteScript("return arguments[0].outerHTML", Elements(i)) Next i
打印結果為:
<input type="hidden" name="ie" value="utf-8" style="">
<input type="hidden" name="f" value="8" style="">
<input type="hidden" name="rsv_bp" value="1">
<input type="hidden" name="rsv_idx" value="1">
<input type="hidden" name="ch" value="">
<input type="hidden" name="tn" value="baidu">
<input type="hidden" name="bar" value="">
<input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off">
<input type="submit" id="su" value="百度一下" class="bg s_btn">
<input type="hidden" name="rn" value="">
<input type="hidden" name="fenlei" value="256">
<input type="hidden" name="oq" value="">
<input type="hidden" name="rsv_pq" value="bd4732b1000713cd">
<input type="hidden" name="rsv_t" value="4529JtCsqjSZIHJEyqM91vIrGVSEiU8pKf4IB+GlY7LKoyYC6BjG63llixk">
<input type="hidden" name="rqlang" value="cn">
<input type="hidden" name="rsv_enter" value="1">
<input type="hidden" name="rsv_dl" value="ib" style="">