【问题标题】:Can't parse a specific value of an item from a webpage using vba无法使用 vba 解析网页中某个项目的特定值
【发布时间】:2020-03-05 16:35:29
【问题描述】:

我在 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") 时有什么问题。

【问题讨论】:

    标签: excel vba web-scraping queryselector


    【解决方案1】:

    如果 .querySelector() 支持 :nth-of-type(),有什么问题 .querySelector("table:nth-of-type(2) tr") 当我在 脚本不起作用

    当使用Microsoft Internet Controls自动化浏览器(IE8+)并创建HTMLDocument关闭ie.Document时支持。然后,您可以访问极少数的pseudo class selectors。当innerHTML 通过MSXML2.XMLHTTP 提供时,HTMLDocument 不是这种情况。请记住,您输入 HTMLDocument 变量 .innerHTML 的内容在 XHR 中将有所不同,其中 javascript 不会运行,而 IE 将运行 js,浏览器将修改内容/请求其他文件,从而为您留下修改过的文件.document。正如开头所提到的,后者当然还有一个浏览器/文档模式依赖。

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

    我感兴趣的项目(建造年份)的价值并不总是在 相同的索引,所以在这里使用索引是一个错误的想法

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

    IMO 考虑因素是:

    • 相同的 URI,但页面上的替代元素具有更短、希望更健壮的选择器;
    • 不同的 XHR URI,其中所需元素与更健壮的选择器相关联,例如唯一标识;
    • script 标记带有一个很好的正则表达式可抓取字符串 (var yearBuilt = 1234;);
    • 一种具有较少依赖性和/或根据经验具有较高稳定性概率的定位策略

    另外,

    • 针对更快的检索进行了优化

    我认识到以上内容是对同一整体想法的重新散列。

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

    MAIN AREA 关联的构建年份仅出现在文档中的一个位置。注意:我保留这样的假设,即这是相应标题行的下一行。我没有检查足够多的链接来了解今年的价值是否会因房产区域而异,而且您没有说明哪个是必需的。在此示例中,MAIN AREA 出现在列出构建日期的第一部分。

    该页面似乎没有从其他请求中检索所需的内容,因此替代来源不是很明显。似乎没有专用的公共 API。 search functionality 没有从其 POST 请求中提供必要的信息,downloadable files 有 3-4 个月的滞后,主要是 .txt,并且不提供任何实际机会来更快地识别所需信息(实际上会工作量更大,可靠性更低)。

    这留下了考虑 4。您需要一种方法来定位右表中的右列。 html 具有非常重复的结构,几乎没有漂亮的“钩子”。您明智地选择在trs(ergo 应该在表中)上循环寻找trinnerText 中的关键标题字符串,而不是根据表的关系生成更脆弱的路径。因此,权衡了标题字符串出现在不同列和/或不同表中的风险,以换取较短的遍历路径和移动到假定包含感兴趣数据的下一行的灵活性。

    到目前为止,我认为是不错的选择,尽管我个人会选择将搜索限制在标题 (th),然后再上一级。这里的额外好处是我可以为您的下一部分减轻负担:

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

    在这里,您建立了一个不必要的假设/风险,即您感兴趣的列将永远是倒数第二个。尽管您可以循环所有标题并转到父节点,但我会冒险通过在面板标题中搜索唯一字符串来限制到适当的表,然后在检查标题之前获取 next-sibling 表。它引入了 IMO 关于panel headingtablepanel 内容关系的合理假设。然后,这使我们能够根据table 为标题找到正确的索引,并使用该索引来索引下一行的tds。这减轻了位置不是倒数第二的情况。然后,您可以寻找一些进一步的优化。我将匹配项设置为变量以便更快地引用。

    尽管有两个循环结构,但代码行数增加但不会增加复杂性,在正确元素上匹配的安全性更高,合适的退出策略和更少的循环(由于表的目标)。

    总体而言,您的策略是一个不错的策略。我个人会冒着尝试获得正确表格的风险,而不是假设正确的列是倒数第二列。我采用了稍微不同的关系并动态确定了正确的索引。 我对解决方案并不完全满意,但感觉还不错。


    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. Microsoft HTML 对象库
    2. Microsoft XML v(n) '您的版本

    【讨论】:

    • 我的英语有点失落,但我认为意思很清楚。最初的策略是一个很好的策略,但它只是一种权衡。
    • 哎呀...好地方...我先写了一个不同的策略并忘记删除该引用!
    猜你喜欢
    • 2018-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多