VBA HTML抓取错误“对象变量或未设置变量”

时间:2018-09-22 01:46:22

标签: vba debugging indexing web-scraping

我正在跟踪由WiseOwlTutorials频道制作的此视频https://www.youtube.com/watch?v=sGw6r5GVA5g&t=2803s的教程,并陷入了他在视频36:00位置解释的列出程序。

那时候,他开始解释如何通过称为Sub ListVideosOnPage(VidCatName As String, VidCatURL As String)的迭代方法从特定类别返回视频网址和视频列表名称,该方法在另一个模块中使用,该模块循环浏览其网站的所有视频类别主视频页面https://www.wiseowl.co.uk/videos(左上角菜单列表)。

此过程开始时,它将进入每个视频类别的内部,并从该类别中获取每个视频的名称和URL,以便将其列出在页面上,该页面在上述Youtube视频的该部分中是调试页面。但是,实际的WiseOwl视频页面与制作教程视频时的页面不同。

因此,为了将正确的元素放在debbugin页面上,我稍微改变了他的方法,如下所示:

Sub ListVideosOnPage(VidCatName As String, VidCatURL As String)

Dim XMLReq As New MSXML2.XMLHTTP60
Dim HTMLDoc As New MSHTML.HTMLDocument

Dim VidTables As MSHTML.IHTMLElementCollection
Dim VidTable As MSHTML.IHTMLElement
Dim VidRows As MSHTML.IHTMLElementCollection
Dim VidRow As MSHTML.IHTMLElement
Dim VidLink As MSHTML.IHTMLElement    

    XMLReq.Open "GET", VidCatURL, False
    XMLReq.send

    If XMLReq.Status <> 200 Then
        MsgBox "Problem" & vbNewLine & XMLReq.Status & " - " & XMLReq.statusText
        Exit Sub
    End If

    HTMLDoc.body.innerHTML = XMLReq.responseText
        'get the table element in each video category found by other module
        'VidTables tag added by me to get the new element on the WiseOwl website
        Set VidTables = HTMLDoc.getElementsByTagName("table")
        'loop starts to search for row and link tags on the current table
        For Each VidTable In VidTables
            Set VidRows = VidTable.getElementsByTagName("tr")
            For Each VidRow In VidRows
                Set VidLink = VidRow.getElementsByTagName("a")(0) 'just pick the first link
                Debug.Print VidRow.innerText, VidRow.getattribute("href") 'objetc variable not set error happpens here
            Next VidRow
        Next VidTable           
End Sub

我找到了一种方法,可以通过更改vidrow循环内的代码,在代码中添加手动索引以仅获取每一行的第一个链接来规避此Object Variable or With Variable not set错误:

       For Each VidTable In VidTables
            Set VidRows = VidTable.getElementsByTagName("tr")
            For Each VidRow In VidRows
                Index = 0
                For Each VidLink In VidLinks
                   If Index = 0 Then
                            Debug.Print VidLink.innerText, VidLink.getAttribute("href")
                            Index = Index + 1
                   End If
                Next VidLink
            Next VidRow
        Next VidTable

但是,在上述参考视频中,讲师以如下所示的方式对索引进行编码时并没有出现此错误:

        VidLink = VidRow.getElementsByTagName("a")(0) 
        Debug.Print VidRow.innerText, VidRow.getattribute("href")

所以我的问题是如何获取这些未设置错误的对象变量,而在教学视频中讲师没有这样做?在我看来,这是相同的代码,每个元素都以正确的方式定义,并且使用if进行编码的效率更高。谁会更习惯VBA,请帮忙回答一下吗?也许我错过了一些东西。

1 个答案:

答案 0 :(得分:1)

tl:dr:

  1. 我首先为您提供调试和修复信息;
  2. 我继续向您展示使用CSS选择器定位页面样式的另一种方式。这generally faster,更健壮,更灵活;
  3. VidCatName似乎没有被使用,但我现在已经离开了。我个人将删除,除非您以后将开发使用此变量的代码。第二个子参数是passed by value,因此我在签名中添加了ByVal

调试:

您的错误是因为您正在循环所有表行,并尝试访问a标记和href属性。每个表的第一行是标题行,它没有a标签元素,也没有关联的href属性。参见下图:

页面上的表格元素:

enter image description here

请注意,表中的第一个tr标记元素包含一个子th标记元素,指示它是表头,并且没有关联的a标记元素。 / p>

有点像您在该视频的其他地方看到的那样,您想要将循环更改为For Next,然后在这种情况下,从索引1开始以跳过标题行。

因此,包含以下行的部分:For Each VidRow In VidRows变为以下内容:

Dim VidRowID As Long
For Each VidTable In VidTables
    Set VidRows = VidTable.getElementsByTagName("tr")
    For VidRowID = 1 To VidRows.Length - 1 'first row is actually header which doesn't have an a tag or href
        Set VidLink = VidRows(VidRowID).getElementsByTagName("a")(0) 
        Debug.Print VidLink.innerText, VidLink.getAttribute("href") 
    Next VidRowID
Next VidTable

每页只有一个表,因此在这种情况下,所有表的循环都是不必要的代码。


完整调用示例(仅将代码与循环类型的更改一起使用):

Option Explicit        
Public Sub test()    
    ListVideosOnPage "Business Intelligence (70)", "https://www.wiseowl.co.uk/business-intelligence/videos/"    
End Sub

Public Sub ListVideosOnPage(ByVal VidCatName As String,ByVal VidCatURL As String)  
    Dim XMLReq As New MSXML2.XMLHTTP60
    Dim HTMLDoc As New MSHTML.HTMLDocument    
    Dim VidTables As MSHTML.IHTMLElementCollection
    Dim VidTable As MSHTML.IHTMLElement
    Dim VidRows As MSHTML.IHTMLElementCollection
    Dim VidRow As MSHTML.IHTMLElement
    Dim VidLink As MSHTML.IHTMLElement

    XMLReq.Open "GET", VidCatURL, False
    XMLReq.send

    If XMLReq.Status <> 200 Then
        MsgBox "Problem" & vbNewLine & XMLReq.Status & " - " & XMLReq.statusText
        Exit Sub
    End If

    HTMLDoc.body.innerHTML = XMLReq.responseText
    Set VidTables = HTMLDoc.getElementsByTagName("table") 'Should limit to just one table
    Dim VidRowID As Long
    For Each VidTable In VidTables
        Set VidRows = VidTable.getElementsByTagName("tr")
        For VidRowID = 1 To VidRows.Length - 1 'first row is actually header which doesn't have an a tag or href
            Set VidLink = VidRows(VidRowID).getElementsByTagName("a")(0) 
            Debug.Print VidLink.innerText, VidLink.getAttribute("href") 
        Next VidRowID
    Next VidTable
End Sub

CSS选择器:

我会改用CSS selector combination来定位目标父a元素内的table标签元素。这被写为.bpTable a。此组合的更正式术语是descendant selector

  

后代组合器-通常用单个空格(   )字符-组合两个选择器,使与   如果祖先元素匹配,则选择第二个选择器   第一个选择器。利用后代组合器的选择器是   称为后代选择器。

.bpTable实际上本身就是class selector(例如.getElementsByClassName)。前导"."表示的类部分。因此,类名称为bpTable的元素;这是每个页面上目标表的类名。

页面上的目标表元素:

enter image description here

通过.querySelectorAll的{​​{1}}方法应用此选择器,并返回一个静态.document。然后,您可以将此nodeList的{​​{1}}从.Length循环到nodeList,按索引访问元素。

0

参考(VBE>工具>参考):

  1. Microsoft HTML对象库
  2. Microsoft XML V6.0'对于我的Excel 2016版本