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

How to hold a reference to the items matched by `querySelectorAll`, in a variable, that allows you to access its methods?

简介:

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


访问DispStaticNodeList方法:

传统上,至少根据我的经验,持有对 DispStaticNodeList 的引用是一种规范,这就是 querySelectorAll returns,在一个通用的后期-绑定 Object 类型:

例如

Dim nodeList1 As Object

Set nodeList1 = html.querySelectorAll("a")

其中 htmlMSHTML.HTMLDocument 的实例。

正如您从 Locals window 中看到的那样,您得到了预期的 nodeList 显示:

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

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

现在发生了什么?

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

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

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


你能做什么:

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

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 解析器。因此,您可以更快地匹配,结合更具表现力和简洁的语法(none 那些长链方法调用,例如 getElementsByClassName("abc")(0).getElementsByTagName("def")(0).....),能够 return 更多所需节点,而无需重复调用(在前面的示例中,您只会将 def 作为带有 class abc 的第一个元素的子元素,而不是带有 def 标签的所有元素的所有子元素 [=] 128=] abc,你会得到 querySelectorAll(".abc def")。而且,你失去了为节点匹配指定更复杂和特定模式的灵活性,例如 querySelectorAll(".abc > def + #ghi)。对于那些感兴趣的人,你可以在 MSDN.

上阅读有关这些选择器的更多信息

问题:

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

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


我的设置:

OS Name Microsoft Windows 10 Pro
Version 10.0.19042 Build 19042
System Type x64-based PC
Microsoft® Excel® 2019 MSO (16.0.13929.20206) 32-bit (Microsoft Office Professional Plus)
Version 2104 Build 13929.20373
mshtml.dll info as per image

不受影响(待定):

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

不要绝望 VBA 网络抓取工具(我知道有一些!)我们仍然可以享受 css 选择器的奢侈和好处,尽管在 VBA,他们带来了。

救援:

MSHTMLgratias IE,提供了多个scripting object interfaces . One of which is the IHTMLDOMChildrenCollection interface, which inherits from IDispatch,其中:

provides methods to access items in the collection.

这包括 .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

N.B.还有那个;如果您尝试 For Each 例如

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

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

正在更新您的旧 Questions/Answers:

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