您的错误是因为您正在对集合调用文档/节点方法。
Set allRowOfData = appIE.document.getElementsByClassName("strat").getElementsByTagName("tbody")(183)
getElementsByClassName("strat") 返回一个你需要索引的集合,然后使用方法getElementsByTagName。
例如
Set allRowOfData = appIE.document.getElementsByClassName("strat")(0).getElementsByTagName("tbody")(183)
总结方法:
由于不同futures的数据排列在一长串表行(tr)节点中,需要通过前导future标题(th)确定trs的正确块,检查随后的兄弟trs 以获得正确的mmmyy,然后从行中提取右列(td)值。需要在下一个future 块的开头或兄弟trs 的结尾处停止;以先到者为准。
tl;dr;
HTML 不适合快速识别正确的trs;你使用的索引越多,程序就越脆弱。以下确实有假设,但更稳健。
我使用XMLHTTP request,因为不需要使用浏览器。您有一个未来感兴趣的变量,所有标头ths 都被className 使用css 类选择器收集到nodeList 中,该nodeList 循环直到找到目标future。这会将您置于感兴趣的第一行的开头。各种mmmyy 然后在后续行中。我通过使用辅助函数检查表标题并与定义的目标标题名称进行比较来确定适当的列索引。该函数返回找到标头的适当索引,如果未找到则返回-1。
现在,我有一本字典,其中包含 mmmyy 感兴趣的时期。我循环所有tds,直到我点击下一部分(即下一个tr,它的th有一个标题(th)作为它的FirstChild)。我连续检查每个FirstChild,如果找到的mmmyy 值在字典中,我会使用适当的列值更新字典。
在循环中,我的工作级别低于HTMLDocument,因此,为了利用querySelectorAll,我将当前的nextNode.NextSibling.OuterHTML 转储到代理 HTMLDocument 变量中;然后我可以再次访问querySelectorAll,并且可以按索引选择适当的td。我需要将传输的 html 包装在 <TABLE><TD></TABLE> 标签中,以便 HTML 解析器不会抱怨并获得正确的 #td 元素。可以肯定的是,如果我有时间重新审视这一点,我可能会加强这一点。
最后,我将字典值写到工作表中。
VBA:
Option Explicit
Public Sub GetCocoaClosePrices()
Dim html As MSHTML.HTMLDocument, targetPeriods As Object, targetFuture As String, targetColumnName As String
targetFuture = "London Cocoa(LCE)"
targetColumnName = "Close"
Set targetPeriods = CreateObject("Scripting.Dictionary")
Set html = New MSHTML.HTMLDocument
targetPeriods.Add "Dec20", "Not found"
targetPeriods.Add "Mar21", "Not found"
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://www.mrci.com/ohlc/ohlc-all.php", False
.Send
html.body.innerHTML = .responseText
End With
Dim tableHeaders As Object, targetColumnNumber As Long
Set tableHeaders = html.querySelectorAll("tr ~ tr ~ tr .colhead")
targetColumnNumber = GetTargetColumnNumber(tableHeaders, targetColumnName)
If targetColumnNumber = -1 Then Exit Sub
Set targetPeriods = GetUpdatedDictionary(targetPeriods, html, targetFuture, targetColumnNumber)
With ThisWorkbook.Worksheets(1)
.Cells(1, 1).Resize(1, targetPeriods.Count) = targetPeriods.keys
.Cells(2, 1).Resize(1, targetPeriods.Count) = targetPeriods.items
End With
End Sub
Public Function GetUpdatedDictionary(ByRef targetPeriods As Object, ByVal html As HTMLDocument, ByVal targetFuture As String, ByVal targetColumnNumber As Long) As Object
Dim html2 As MSHTML.HTMLDocument, firstChild As Object, i As Long
Dim nextNode As Object, headerNodes As Object
Set headerNodes = html.querySelectorAll(".note1")
Set html2 = New MSHTML.HTMLDocument
For i = 0 To headerNodes.Length - 1
If headerNodes.Item(i).innerText = targetFuture Then 'find the right target future header
Set nextNode = headerNodes.Item(i).ParentNode 'move up to the parent tr node
Do 'walk the adjacent tr nodes
Set nextNode = nextNode.NextSibling
Set firstChild = nextNode.firstChild
If nextNode Is Nothing Then
Set GetUpdatedDictionary = targetPeriods
Exit Function 'exit if no next section
End If
html2.body.innerHTML = "<TABLE><TD>" & nextNode.outerHTML & "</TABLE>"
If targetPeriods.Exists(firstChild.innerText) Then
targetPeriods(firstChild.innerText) = html2.querySelectorAll("td").Item(targetColumnNumber).innerText
End If
Loop While firstChild.tagName <> "TH" 'stop at next section i present
End If
Next
Set GetUpdatedDictionary = targetPeriods
End Function
Public Function GetTargetColumnNumber(ByVal nodeList As Object, ByVal targetColumnName As String) As Long
Dim i As Long
For i = 0 To nodeList.Length - 1
If nodeList.Item(i).innerText = targetColumnName Then
GetTargetColumnNumber = i + 1 'to account for th
Exit Function
End If
Next
GetTargetColumnNumber = -1
End Function
阅读:
- css selectors
- document.querySelectorAll
- Node.nextSibling
- Node.parentNode
参考资料(VBE>工具>参考资料):
- Microsoft HTML 对象库