如何在变量中保存对由 `querySelectorAll` 匹配的项目的引用,以允许您访问其方法?

问题描述

简介:

你们中的一些人可能已经注意到,与 MSHTML.HTMLDocument 中的 MSHTML.DllquerySelectorAll 方法相关的某些问题(通过 Microsoft HTML Document Library 引用)。我相信,这发生在上个月。它可能不会影响所有用户,当我获得有关哪些版本等受到影响的更多信息时,我将更新此问答。请随时在下面评论您的设置以及是否适用于后期绑定和早期绑定(根据答案中的代码


访问 dispStaticNodeList 方法

传统上,至少在我的经验中,在泛型后期绑定 Object 类型中持有对 dispStaticNodeList 的引用是常态,这是 querySelectorAll 返回的内容:

例如

Dim nodeList1 As Object

Set nodeList1 = html.querySelectorAll("a")

其中 htmlMSHTML.HTMLDocument 的实例。

正如您在 Locals 窗口中看到的那样,您会看到预期的 nodeList

enter image description here

然后,您可以访问与指定选择器组匹配的文档元素列表,使用 .item(index),并获取.Length 匹配的项目数。例如

Debug.Print nodeList1.item(0).innerText
Debug.Print nodeList1.Length

现在发生了什么?

尝试通过后期绑定 Object 及其底层接口访问方法会导致 Object required(使用 .item() 方法调用时)或 Null查询 .Length() 时。例如

nodeList1.item(0).innertext  ' => Run-time error '424': Object required
Debug.Print nodeList1.Length ' => Null 

当您通过分配给变量来持有引用时会发生这种情况。


您可以做什么:

您可以使用 With 并处理 html,避免使用 Object

With html.querySelectorAll("a")
    For i = 0 To .Length - 1
       Debug.Print .Item(i).innerText
    Next
End With

所以,我认为问题主要在于 Object 数据类型及其底层接口。并且可能,与 MSHTML 相关的某些事情已经被破坏,而且很可能是现在不再受支持的 Internet Explorer,它位于后台

然而,这是不可取的,因为您在循环期间解析和重新解析相同的 HTML,失去了通过选择 css 选择器而不是传统方法获得的大部分效率,例如getElementsByClassName。那些传统方法保持不变。


为什么我们中的一些人会关心?

现代浏览器(甚至 IE8 以后)通过使用 css 选择器支持更快的节点匹配。假设这通过 MSHTML.HTMLDocument 延续到 DOM 解析器似乎是合理的。所以,你有更快的匹配,结合更有表现力和简洁的语法(没有那些长链方法调用,例如 getElementsByClassName("abc")(0).getElementsByTagName("def")(0).....),能够返回更多所需的节点,无需重复调用(在前面的例子中,你只会得到def 作为类为 abc 的第一个元素的子元素,而不是所有类为 def 的元素的所有带有标记 abc 的子元素,您可以使用 {{ 1}}。而且,您失去了为节点匹配指定更复杂和特定模式的灵活性,例如 querySelectorAll(".abc def")。对于那些感兴趣的人,您可以在 MSDN 上阅读有关这些选择器的更多信息。


问题:

那么,如何避免重新解析,并保留对返回的匹配节点列表的引用?尽管进行了大量搜索,但我在互联网上没有找到任何记录最近行为变化的内容。这也是最近发生的变化,可能只会影响一小部分用户群。

我希望以上内容满足证明对问题进行研究的需要。


我的设置:

querySelectorAll(".abc > def + #ghi)

不受影响(待定):

  1. Office Professional plus 2013。Win 7,32 位,MSHTML.dll 11.0.9600.19597

解决方法

不要对 VBA 网络爬虫感到失望(我知道有一些!)我们仍然可以享受 css 选择器的奢侈和好处,虽然在 VBA 中确实有些限制,但它们带来的好处。

救援:

MSHTML免费 IE,提供了许多 scripting object interfaces 。其中之一是 IHTMLDOMChildrenCollection 接口,它继承自 IDispatch,并且:

提供访问集合中项目的方法。

这包括 .Length 属性和通过 .item(index) 访问项目。

Dim nodeList2 As MSHTML.IHTMLDOMChildrenCollection

Set nodeList2 = html.querySelectorAll("a")
Debug.Print nodeList2.Length                 ' => n 
Debug.Print nodeList2.Item(0).innerText

这在 Windows XP + 客户端和 Windows 2000 Server 以后的服务器上受支持。


VBA:

Public Sub ReviewingNodeListMethods()
    '' References (VBE > Tools > References):
          ''Microsoft HTML object Library
          ''Microsoft XML library (v.6 for me)

    Dim http As MSXML2.XMLHTTP60,html As MSHTML.HTMLDocument   'XMLHTTP60 is for Excel 2016. Change according to your version e.g. XMLHTTP for 2013)
    
    Set http = New MSXML2.XMLHTTP60: Set html = New MSHTML.HTMLDocument
    
    With http
        .Open "GET","http://books.toscrape.com/",False
        .send
        html.body.innerHTML = .responseText
    End With

    Dim nodeList1 As Object,nodeList2 As MSHTML.IHTMLDOMChildrenCollection
    
    Set nodeList1 = html.querySelectorAll("a")
    Set nodeList2 = html.querySelectorAll("a")
  
    Debug.Print nodeList1.Length                 ' => Null
    Debug.Print nodeList2.Length                 ' => 94
    
    Debug.Print nodeList2.Item(0).innerText
    
    '    Dim i As Long
    '
    '    With html.querySelectorAll("a")
    '        For i = 0 To .Length - 1
    '           Debug.Print .Item(i).innerText
    '        Next
    '    End With
    
    '' ================Warning: This will crash Excel -============================

    '    Dim node As MSHTML.IHTMLDOMNode
    '
    '    For Each node In nodeList2
    '        Debug.Print node.innerText
    '    Next
    '' ================Warning: This will crash Excel -============================

End Sub

注意还有underlying problem of the collection enumeration method;如果您尝试 For Each 例如

,它会导致 Excel 崩溃
Dim node As MSHTML.IHTMLDOMNode

For Each node In nodeList2
    Debug.Print node.innerText
Next

更新旧的问题/答案:

  1. 您可以使用此 SEDE query 来确定潜在的修订对象。输入您的用户 ID 和搜索词“querySelectorAll”
  2. 或者只需在搜索栏中使用以下内容: querySelectorAll user:<userid> is:answer; querySelectorAll user:<userid> is:question