首页 > 解决方案 > 如何在允许您访问其方法的变量中保存对由 `querySelectorAll` 匹配的项目的引用?

问题描述

介绍:

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


访问DispStaticNodeList方法:

传统上,至少根据我的经验,在通用的后期绑定类型中持有对 的引用是常态DispStaticNodeList,这是返回的内容:querySelectorAllObject

例如

Dim nodeList1 As Object

Set nodeList1 = html.querySelectorAll("a")

html的实例在哪里MSHTML.HTMLDocument

正如您从 Locals 窗口中看到的那样,您会得到预期的nodeList显示:

在此处输入图像描述

然后,您可以访问与指定选择器组匹配的文档元素列表.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 选择器支持更快的节点匹配。假设这在 DOM 解析器中使用MSHTML.HTMLDocument. 因此,您可以更快地匹配,结合更具表现力和简洁的语法(没有那些长链方法调用,例如getElementsByClassName("abc")(0).getElementsByTagName("def")(0).....),返回更多所需节点的能力,无需重复调用(在前面的示例中,您只会def作为第一个的子节点带有 class 的元素abc,而不是带有标签def的所有子元素,所有带有 class 的元素abc,你会得到querySelectorAll(".abc def")。而且,你失去了为节点匹配指定更复杂和特定模式的灵活性,例如querySelectorAll(".abc > def + #ghi)。对于那些感兴趣的人,你可以阅读更多关于MSDN上的选择器.


问题:

那么,如何避免重新解析,并保持对返回的匹配节点列表的引用呢?尽管进行了相当多的搜索,但我在互联网上没有发现任何东西可以记录最近的行为变化。这也是一个最近的变化,可能只影响一小部分用户群。

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


我的设置:

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

标签: vbaweb-scrapingmshtmlselectors-apinodelist

解决方案


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

救援:

MSHTML感谢IE,提供了许多脚本对象接口。其中之一是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

NB仍然存在集合枚举方法的底层问题;如果您尝试For Each例如,它会导致 Excel 崩溃

Dim node As MSHTML.IHTMLDOMNode

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

更新您的旧问题/答案:

  1. 您可以使用此SEDE 查询来识别潜在的修订候选者。输入您的用户 ID 和搜索词“querySelectorAll”
  2. 或者干脆在搜索栏中使用以下内容 querySelectorAll user:<userid> is:answerquerySelectorAll user:<userid> is:question

推荐阅读