《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="">

 

posted @ 2020-09-19 16:15  ryueifu  阅读(4337)  评论(0编辑  收藏  举报