《SeleniumBasic 3.141.0.0 - 在VBA中操作浏览器》系列文章之十九:执行JavaScript脚本
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="">