【问题标题】:Use getElementById on HTMLElement instead of HTMLDocument在 HTMLElement 上使用 getElementById 而不是 HTMLDocument
【发布时间】:2013-02-17 23:13:48
【问题描述】:

我一直在尝试使用 VBS/VBA 从网页中抓取数据。

如果是 Javascript,我会很轻松地离开,但在 VBS/VBA 中似乎并不那么直接。

这是我为回答而制作的示例,它可以工作,但我曾计划使用getElementByTagName 访问子节点,但我不知道如何使用它们! HTMLElement 对象没有这些方法。

Sub Scrape()
Dim Browser As InternetExplorer
Dim Document As HTMLDocument
Dim Elements As IHTMLElementCollection
Dim Element As IHTMLElement

Set Browser = New InternetExplorer

Browser.navigate "http://www.hsbc.com/about-hsbc/leadership"

Do While Browser.Busy And Not Browser.readyState = READYSTATE_COMPLETE
    DoEvents
Loop

Set Document = Browser.Document

Set Elements = Document.getElementsByClassName("profile-col1")

For Each Element in Elements
    Debug.Print "[  name] " & Trim(Element.Children(1).Children(0).innerText)
    Debug.Print "[ title] " & Trim(Element.Children(1).Children(1).innerText)
Next Element

Set Document = Nothing
Set Browser = Nothing
End Sub

我一直在查看 HTMLElement.document 属性,看看它是否像文档的一个片段,但它要么难以使用,要么不是我想的那样

Dim Fragment As HTMLDocument
Set Element = Document.getElementById("example") ' This works
Set Fragment = Element.document ' This doesn't

这似乎也是一种冗长的方式(尽管这通常是 vba imo 的方式)。 任何人都知道是否有更简单的方法来链接函数?

Document.getElementById("target").getElementsByTagName("tr") 会很棒...

【问题讨论】:

    标签: vba web-scraping


    【解决方案1】:
    Sub Scrape()
        Dim Browser As InternetExplorer
        Dim Document As htmlDocument
        Dim Elements As IHTMLElementCollection
        Dim Element As IHTMLElement
    
        Set Browser = New InternetExplorer
        Browser.Visible = True
        Browser.navigate "http://www.stackoverflow.com"
    
        Do While Browser.Busy And Not Browser.readyState = READYSTATE_COMPLETE
            DoEvents
        Loop
    
        Set Document = Browser.Document
    
        Set Elements = Document.getElementById("hmenus").getElementsByTagName("li")
        For Each Element In Elements
            Debug.Print Element.innerText
            'Questions
            'Tags
            'Users
            'Badges
            'Unanswered
            '
        Next Element
    
        Set Document = Nothing
        Set Browser = Nothing
    End Sub
    

    【讨论】:

      【解决方案2】:

      我也不喜欢。

      所以使用javascript:

      Public Function GetJavaScriptResult(doc as HTMLDocument, jsString As String) As String
      
          Dim el As IHTMLElement
          Dim nd As HTMLDOMTextNode
      
          Set el = doc.createElement("INPUT")
          Do
              el.ID = GenerateRandomAlphaString(100)
          Loop Until Document.getElementById(el.ID) Is Nothing
          el.Style.display = "none"
          Set nd = Document.appendChild(el)
      
          doc.parentWindow.ExecScript "document.getElementById('" & el.ID & "').value = " & jsString
      
          GetJavaScriptResult = Document.getElementById(el.ID).Value
      
          Document.removeChild nd
      
      End Function
      
      
      Function GenerateRandomAlphaString(Length As Long) As String
      
          Dim i As Long
          Dim Result As String
      
          Randomize Timer
      
          For i = 1 To Length
              Result = Result & Chr(Int(Rnd(Timer) * 26 + 65 + Round(Rnd(Timer)) * 32))
          Next i
      
          GenerateRandomAlphaString = Result
      
      End Function
      

      如果您对此有任何问题,请告诉我;我已将上下文从方法更改为函数。

      顺便问一下,你用的是什么版本的IE?我怀疑您使用的是

      编辑

      评论回复不是真正的评论: 基本上在 VBA 中执行此操作的方法是遍历子节点。问题是你没有得到正确的返回类型。您可以通过创建自己的类(分别)实现 IHTMLElement 和 IHTMLElementCollection 来解决此问题;但这对我来说太痛苦了,无法获得报酬:)。如果您下定决心,请阅读 VB6/VBA 的 Implements 关键字。

      Public Function getSubElementsByTagName(el As IHTMLElement, tagname As String) As Collection
      
          Dim descendants As New Collection
          Dim results As New Collection
          Dim i As Long
      
          getDescendants el, descendants
      
          For i = 1 To descendants.Count
              If descendants(i).tagname = tagname Then
                  results.Add descendants(i)
              End If
          Next i
      
          getSubElementsByTagName = results
      
      End Function
      
      Public Function getDescendants(nd As IHTMLElement, ByRef descendants As Collection)
          Dim i As Long
          descendants.Add nd
          For i = 1 To nd.Children.Length
              getDescendants nd.Children.Item(i), descendants
          Next i
      End Function
      

      【讨论】:

      • 我试试看,我尝试了类似的方法,导航到我的页面,然后导航到 javascript: 网址。工作,但不是很好。你知道Document.parentWindow.ExecScript 是否被阻塞?或者我的脚本可能在结果设置之前没有完成执行? (也会在一秒钟内测试自己)。不过,我仍然想知道是否有一种方法可以完全使用 VB!
      • 这并不是说 querySelector 不适用于 IE9+ dll,我只是没有测试过这些
      • @NickSlash 我已编辑我的答案以回复您的评论。至于阻塞,我想是的,但我不确定。应该相当容易测试(几个嵌套循环计数为 2^31 或 JS 中的整数最大值)。
      • 嗯,创建类和其他东西听起来确实需要做很多工作!你的第二个例子似乎更符合我的要求,尽管使用 javascript 看起来更诱人!另外,我有 IE9,我想我需要深入了解HTMLDocument,因为我没有看到很多内容。
      • 你有querySelector吗?奇怪,我有 IE8,没有 getElementsByClassName,但在以前版本的 shdocvw.dll 中有它;所以假设你的年龄更大。
      【解决方案3】:

      我会使用 XMLHTTP 请求来更快地检索页面内容。然后很容易使用 querySelectorAll 来应用 CSS 类选择器来按类名抓取。然后通过标签名和索引访问子元素。

      Option Explicit
      Public Sub GetInfo()
          Dim sResponse As String, html As HTMLDocument, elements As Object, i As Long
      
          With CreateObject("MSXML2.XMLHTTP")
              .Open "GET", "https://www.hsbc.com/about-hsbc/leadership", False
              .setRequestHeader "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"
              .send
              sResponse = StrConv(.responseBody, vbUnicode)
          End With
          Set html = New HTMLDocument
          With html
              .body.innerHTML = sResponse
              Set elements = .querySelectorAll(".profile-col1")
              For i = 0 To elements.Length - 1
                  Debug.Print String(20, Chr$(61))
                  Debug.Print elements.item(i).getElementsByTagName("a")(0).innerText
                  Debug.Print elements.item(i).getElementsByTagName("p")(0).innerText
                  Debug.Print elements.item(i).getElementsByTagName("p")(1).innerText
              Next
          End With
      End Sub
      

      参考资料:

      VBE > 工具 > 参考 > Microsoft HTML 对象库

      【讨论】:

        【解决方案4】:

        感谢 dee 使用 Scrape() 子例程提供的上述答案。代码按照编写的方式完美运行,然后我能够将代码转换为与我试图抓取的特定网站一起使用。

        我没有足够的声誉来投票或发表评论,但实际上我确实有一些小的改进可以添加到 dee 的答案中:

        1. 您需要通过“Tools\References”将 VBA 引用添加到“Microsoft HTML 对象库”,以便编译代码。

        2. 我将 Browser.Visible 行注释掉并添加如下注释

          'if you need to debug the browser page, uncomment this line:
          'Browser.Visible = True
          
        3. 我在 Set Browser = Nothing 之前添加了一行关闭浏览器:

          Browser.Quit
          

        再次感谢迪!

        ETA:这适用于 IE9 的机器,但不适用于 IE8 的机器。谁有解决办法?

        我自己找到了修复,所以回到这里发布它。 ClassName 函数在 IE9 中可用。为了在 IE8 中工作,您可以使用 querySelectorAll,在您要查找的对象的类名前加一个点:

        'Set repList = doc.getElementsByClassName("reportList") 'only works in IE9, not in IE8
        Set repList = doc.querySelectorAll(".reportList")       'this works in IE8+
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-06-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多