14

我一直在使用 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")会很棒...

4

4 回答 4

13
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
        'Ask Question
    Next Element

    Set Document = Nothing
    Set Browser = Nothing
End Sub
于 2013-03-04T00:44:13.873 回答
5

我也不喜欢。

所以使用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?我怀疑您使用的是< IE8。如果您升级到 IE8,我想它会将 shdocvw.dll 更新为 ieframe.dll,您将能够使用 document.querySelector/All。

编辑

不是真正的评论的评论响应:基本上在 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
于 2013-03-03T23:40:32.910 回答
2

我会使用 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 对象库

于 2018-10-25T20:00:37.603 回答
1

感谢 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+
于 2014-03-18T22:00:31.653 回答