无法使用 vba 解析网页中项目的特定值

Can't parse a specific value of an item from a webpage using vba

我在 VBA 中创建了一个脚本来从网页中获取特定项目。我感兴趣的项目(Year Built)的值并不总是在同一个索引中,所以在这里使用索引是一个错误的想法。我在下面给出两个链接只是因为项目的价值在两个网页中处于不同的索引中。

site one

site two

我最初获取该值的方法是:

.NextSibling.getElementsByTagName("td")(3).innerText

我所追求的值显示为:

我现在正在尝试的方法(有效,但位置仍然是假设的,如果位置改变会中断):

.NextSibling.LastChild.PreviousSibling.innerText

到目前为止我创建了:

Sub GetInformation()
    Dim Http As New XMLHTTP60, links, i&
    Dim Htmldoc As New HTMLDocument, link
    Dim Wb As Workbook, ws As Worksheet, r&

    Set Wb = ThisWorkbook
    Set ws = Wb.Worksheets("Sheet1")

    links = Array( _
        "https://esearch.brazoscad.org/Property/View/114414", _
        "https://esearch.brazoscad.org/Property/View/117608" _
       )

    For Each link In links
        With Http
            .Open "GET", link, False
            .send
            Htmldoc.body.innerHTML = .responseText
        End With


        With Htmldoc.querySelectorAll("tr")
            For i = 0 To .Length - 1
                If InStr(.item(i).innerText, "Year Built") > 0 Then
                    r = r + 1: ws.Cells(r, 1) = .item(i).NextSibling.LastChild.PreviousSibling.innerText
                End If
            Next i
        End With
    Next link
End Sub

如何从网页中获取项目的特定值?

顺便说一下,如果 .querySelector() 支持 :nth-of-type(),当我在脚本中使用它时 .querySelector("table:nth-of-type(2) tr") 有什么问题,它不起作用。

if .querySelector() supports :nth-of-type(), what's wrong with .querySelector("table:nth-of-type(2) tr") when I use it within the script which doesn't work

在使用 Microsoft Internet Controls 自动化浏览器 (IE8+) 时受支持,并在 ie.Document 之外创建了 HTMLDocument。然后您可以访问极少数 pseudo class selectors。当通过 MSXML2.XMLHTTP 提供 innerHTML 时,HTMLDocument 不是这种情况。请记住,您输入 HTMLDocument 变量 .innerHTML 的内容在 XHR 中会有所不同,其中 javascript 不会 运行 与 IE 中的 js 运行 并且浏览器将修改 content/request 个附加文件,给您留下修改后的 .document。如开头所述,后者当然也有 browser/document 模式依赖性。

选择器 table:nth-of-type(2) tr,即使支持,也不适合此处。

The value of the item (Year Built) I'm interested in is not always in the same index, so using index is a wrong idea here

根据对您代码的仔细检查,您试图解释的可变性似乎是目标中列数的潜在差异 table,因此您的元素存在于td 在给定行内的不同索引处(例如,您没有尝试考虑行可变性......)。因此,我们正在寻找一种总体上不同的关系;不需要元素之间的关系;或动态确定适当的索引;或者甚至是这些的组合。

IMO 的考虑是:

  • 相同的 URI,但页面上的替代元素具有更短、希望更强大的选择器;
  • 不同的 XHR URI,其中所需的元素与更强大的选择器相关联,例如一个唯一的ID;
  • 一个 script 标签,带有一个很好的正则表达式可抓取字符串 (var yearBuilt = 1234;);
  • 依赖性较小的仓位策略and/or,根据经验,随时间稳定的可能性更高

此外,

  • 为更快的检索进行了优化

我认识到以上是re-hashings一个总体思路。

查看注意事项和提供的两个链接:

MAIN AREA 关联的建造年份仅出现在文档中的一处。注意:我保留假设这是适当 header 行的下一行。我还没有检查足够多的链接来了解今年的价值是否会因 属性 的区域而异,而且您还没有说明哪个是必需的。 MAIN AREA 在此示例中显示为列出构建日期的第一部分。

该页面似乎没有从其他请求中检索到所需的内容,因此替代来源不是很明显。似乎没有专门的 public API。 search functionality doesn't provide the neccessary info from its POST requests, and the downloadable files 有 3-4 个月的滞后,主要是 .txt 并且没有提供任何现实的机会来更快地识别所需的信息(实际上会做更多的工作并且不太可靠)。

这就剩下考虑 4 了。您需要一种方法来在右侧 table 中定位正确的列。 html 有一个非常重复的结构,很少有好的 'hooks'。与其根据 tables 的关系生成更脆弱的路径,不如明智地选择在 trs 上循环(因此应该在 table 中)寻找键 header tr innerText 中的字符串。因此,权衡了 header 字符串出现在不同列 and/or 不同 table 的风险,以获得更短的遍历路径和移动到假定包含数据的下一行的灵活性兴趣。

到目前为止,我认为是不错的选择,尽管我个人会选择将搜索限制在 header 秒 (th),然后逐步增加到 parent。这里的额外好处是我可以为你的下一部分减轻压力:

.Item(i).NextSibling.LastChild.PreviousSibling.innerText

在这里你建立了一个不必要的 assumption/risk 你感兴趣的栏目总是倒数第二个。尽管您可以循环所有 header 并上升到 parent 节点,但我会冒着通过在 panel-heading 中搜索唯一字符串然后限制为适当 table 的风险在检查 header 之前抓住 next-sibling table。它向 IMO 介绍了关于 panel headingtablepanel 内容关系的合理假设。然后,这允许我们根据 table 找到 header 的正确索引,并使用该索引索引到下一行的 tds。这减轻了位置不是倒数第二的情况。然后您可以寻找一些进一步的优化。我将匹配项设置为变量以加快引用速度。

多了几行代码,但没有增加复杂性,在正确的元素上匹配更安全,suitable 退出策略和更少的循环(由于 table 的目标)尽管有两个循环结构。

总的来说你的策略是好的。我个人愿意冒着尝试正确 table 的风险,而不是假设正确的列是倒数第二列。我采用了稍微不同的关系并动态确定正确的索引。 我对解决方案并不完全满意,但感觉还不错。


VBA:

Option Explicit

Public Sub GetInformation()
    Dim Http As New XMLHTTP60, links, i&
    Dim htmlDoc As New HTMLDocument, link
    Dim Wb As Workbook, ws As Worksheet, r&

    Set Wb = ThisWorkbook
    Set ws = Wb.Worksheets("Sheet1")

    links = Array( _
            "https://esearch.brazoscad.org/Property/View/114414", _
            "https://esearch.brazoscad.org/Property/View/117608" _
            )

    For Each link In links
        With Http
            .Open "GET", link, False
            .send
            htmlDoc.body.innerHTML = .responseText
        End With

        Dim panels As Object, table As Object, headers As Object

        Set panels = htmlDoc.querySelectorAll(".panel-heading")

        For i = 0 To panels.Length - 1
            If InStr(panels.Item(i).innerText, "Property Improvement - Building") > 0 Then
                Set table = panels.Item(i).NextSibling 'assumption on relationship
                Exit For
            End If
        Next i

        If Not table Is Nothing Then

            Set headers = table.getElementsByTagName("th")

            For i = 0 To headers.Length - 1
                If InStr(headers(i).innerText, "Year Built") > 0 Then
                    r = r + 1: ws.Cells(r, 1) = headers(i).ParentNode.NextSibling.Children(i).innerText
                    Exit For
                End If
            Next
        End If
        Set htmlDoc = Nothing: Set table = Nothing
    Next link
End Sub

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

  1. 微软HTMLObject图书馆
  2. Microsoft XML v(n) '你的版本