【问题标题】:How to access the data from a table?如何访问表中的数据?
【发布时间】:2019-02-25 14:18:07
【问题描述】:

表 1:

表2:

我正在尝试从网站上的表格中抓取数据。

有两个表具有相同的表类名称,但不同的 id。

我需要从 ID 为“option”的表中提取数据。

Sub hsiotpions()
    Dim ieObj As InternetExplorer
    Dim htmlEle As IHTMLElement
    Dim i As Integer
    
    i = 1
    
    Set ieObj = New InternetExplorer
    ieObj.Visible = False
    ieObj.navigate "https://www.hkex.com.hk/Market-Data/Futures-and-Options-Prices/Equity-Index/Hang-Seng-Index-Futures-and-Options?sc_lang=en#&product=HSI"
    
    Application.Wait Now + TimeValue("00:00:05")
    
    For Each htmlEle In ieObj.document.getElementById("option").getElementByTagName("tr")
        With ActiveSheet
            .Range("A" & i).Value = htmlEle.Children(0).textContent
            .Range("B" & i).Value = htmlEle.Children(1).textContent
            .Range("C" & i).Value = htmlEle.Children(2).textContent
            .Range("D" & i).Value = htmlEle.Children(3).textContent
            .Range("E" & i).Value = htmlEle.Children(4).textContent
            .Range("F" & i).Value = htmlEle.Children(5).textContent
            .Range("G" & i).Value = htmlEle.Children(6).textContent
            .Range("H" & i).Value = htmlEle.Children(7).textContent
            .Range("I" & i).Value = htmlEle.Children(8).textContent
            .Range("J" & i).Value = htmlEle.Children(9).textContent
            .Range("K" & i).Value = htmlEle.Children(10).textContent
            .Range("L" & i).Value = htmlEle.Children(11).textContent
        End With
        
        i = i + 1
        
    Next htmlEle

End Sub

【问题讨论】:

    标签: excel vba web-scraping


    【解决方案1】:

    您可以使用 id 来选择表格。请务必正确处理第 4 列和第 8 列,以便写入工作表。

    在你的代码中你有一个错误,你没有使用复数:

    getElementByTagName("tr")
    

    应该是这样的

    getElementsByTagName("tr")
    

    返回一个集合。

    表格主体的行实际上具有相同的类名,我使用下面的那些来循环行。

    我不喜欢硬编码的等待时间,但由于我目前看不到页面完全加载时的良好指标,您可以调整以下行,直到获得所有行

    Application.Wait Now + TimeSerial(0, 0, 10)
    

    结束参数是以秒为单位的等待时间。

    Option Explicit    
    Public Sub GetTable()
        Dim ws As Worksheet, ie As Object, table As Object, headers()
        Dim headersTop As Object, ele As Object
    
        Set ws = ThisWorkbook.Worksheets("Sheet1")
        Set ie = CreateObject("InternetExplorer.Application")
        headers = Array("OI", "Volume", "IV", "Bid/Ask", "Last", "Strike", "Last", "Bid/Ask", "IV", "Volume", "OI")  '<== This is second row of headers
    
        With ie
            .Visible = True
            .Navigate2 "https://www.hkex.com.hk/Market-Data/Futures-and-Options-Prices/Equity-Index/Hang-Seng-Index-Futures-and-Options?sc_lang=en#&product=HSI"
    
            While .Busy Or .readyState < 4: DoEvents: Wend
    
            Application.Wait Now + TimeSerial(0, 0, 10)
    
            Set table = .document.querySelector("#option")
            Set headersTop = .document.querySelectorAll("#option tr:first-child th")  '<== This is top row of headers which involves merged table cells. I prepare the excel sheet in the same way in the code below.
            ws.Range("A1:D1").Merge
            ws.Range("A1").Value = headersTop.item(0).innerText  ' CALL
            ws.Range("E1:G1").Merge
            ws.Range("E1") = headersTop.item(1).innerText  '< Date
            ws.Range("H1:K1").Merge
            ws.Range("H1") = headersTop.item(2).innerText  '< PUT
            ws.Cells(2, 1).Resize(1, UBound(headers) + 1) = headers
            Dim r As Long, c As Long, td As Object, tr As Object
            r = 3
            For Each tr In table.getElementsByClassName("tdrow") 'loop the rows below the headers by using class name to isolate
                c = 1
                For Each td In tr.getElementsByTagName("td") '< loop table cells i.e. columns of rows
                    ws.Cells(r, c) = IIf(c Mod 4 = 0, "'" & td.innerText, td.innerText)  '< If column number is 4 or 8 then add "'" in front so formatting preserved
                c = c + 1
                Next
                r = r + 1
            Next
            .Quit
        End With
    End Sub
    

    CSS 选择器:

    我最初使用的是css selectors。现代浏览器针对使用 css(管理页面样式)进行了优化。我们通过querySelectorquerySelectorAll 方法应用css 选择器,在本例中为ie.document

    这是#option tr:first-child th 选择的内容:

    您可以看到它是顶级标题。由于表格中实际上有比这更多的列,我们知道我们已经合并了单元格。

    由于我们想要多个标题,我们使用querySelectorAll 方法,而不是返回单个元素的querySelector,以返回由 ("") 内的选择器指定的匹配元素的nodeList。然后通过从 0 开始的索引访问 nodeList 中的项目。


    表体:

    我已经通过 id 检索了感兴趣的表

    Set table = .document.querySelector("#option")
    

    # 是一个 css id 选择器,在语义上等同于:

    Set table = .document.getElementById("option")
    

    只是,css 选择器方法应该会更快,除非在非常旧的 IE 版本上。

    当我们拥有表格时,我们知道行是tr 元素,td 是表格单元格(列)。

    查看table 变量中的表格行,您会发现我们有两个不同长度的标题:

    这就是为什么我选择不循环这些尝试写出的原因。最下面一行的值是静态的,所以我把它们放在数组变量headers 中写出来。第一行有一个动态的日期值,所以我使用 css 选择器自行检索该行:

    Set headersTop = .document.querySelectorAll("#option tr:first-child th")
    

    并索引到返回的 nodeList 以将项目放置在一个网页布局所指示的各种合并单元格中。

    但请注意,表体行都具有相同的类名:

    所以,我通过在表格行上使用类名来选择这些:

    For Each tr In table.getElementsByClassName("tdrow")
    

    然后我循环这些行中的列:

    For Each td In tr.getElementsByTagName("td")
    

    因为第 4 列和第 8 列具有格式,Excel 将尝试将您作为日期/分数处理,您希望在前面添加“'”以保留格式:

    ws.Cells(r, c) = IIf(c Mod 4 = 0, "'" & td.innerText, td.innerText)
    

    【讨论】:

    • 感谢您的帮助。因为我不熟悉使用 vba 作为我的工具之一,所以您的代码对我来说似乎很先进。我尝试了代码。它确实返回前两行;但是剩余的行没有按预期显示。
    • 我添加了一个我不喜欢的硬编码等待时间,但如果你将它设置得足够高,现在可能还可以。我还在代码中添加了注释。
    • 非常感谢您成为我的 vba 导师。您已经详细介绍了在钓鱼代码中部署的技术。顺便问一下,是否可以在代码中添加一个功能(输入您要查找的期权的执行价格范围,加载数据并将数据导出到excel)?
    • 这是我为上述建议功能添加的代码,但它不起作用。你能看看我吗? Dim obj As Object, startk As Long, endk As Long startk = InputBox("最低行使价:") endk = InputBox("最高行使价:") .document.all.Item("mss_list val valstart").innerText = startk .document.all.Item("mss_list val valend").innerText = endk Application.SendKeys "{ENTER}" .document.getElementsByName("load").Item.Click
    • 嗨,如果你想做一个函数,请提出一个新问题。在那里展示你的尝试并解释什么不起作用。另外,您是否真的要在每次要进行选择时启动 IE。为什么不使用一个按钮单击子程序,一次获得许多不同的选择?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-04
    • 1970-01-01
    • 2020-01-16
    • 2018-07-13
    • 1970-01-01
    • 2011-10-27
    相关资源
    最近更新 更多