Не удается разобрать определенное значение элемента с веб-страницы с помощью vba - PullRequest
1 голос
/ 09 ноября 2019

Я создал скрипт в VBA для получения определенного элемента с веб-страницы. Значение элемента (Year Built), которое меня интересует, не всегда совпадает с одним и тем же индексом, поэтому использование индекса здесь неверная идея. Я даю две ссылки ниже только потому, что стоимость предметов в разных индексах на двух веб-страницах.

сайт один

сайт два

Мой первоначальный подход к получению значения был:

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

Значение, которое я ищу, отображается как:

enter image description here

То, что я сейчас пытаюсь (работает, но позиция все еще гипотетическая и сломается, если позиция изменится):

.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"), когда я его используюв сценарии, который не работает.

1 Ответ

3 голосов
/ 09 ноября 2019

, если .querySelector () поддерживает: nth-of-type (), что не так с .querySelector ("table: nth-of-type (2) tr"), когда я использую его в сценарии, который нене работает

Поддерживается при использовании Microsoft Internet Controls для автоматизации браузера (IE8 +) и создается HTMLDocument off ie.Document. Затем у вас есть доступ к очень небольшому количеству селекторов псевдоклассов . Это не относится к HTMLDocument, когда innerHTML предоставляется через MSXML2.XMLHTTP. Помните, что содержимое, которое вы вводите в переменную HTMLDocument .innerHTML, будет отличаться в XHR, где javascript не будет работать, по сравнению с IE, где будет запускаться js, и браузер будет изменять содержимое / запрашивать дополнительные файлы, оставляя вас с измененным.document. Как уже упоминалось в начале, конечно, есть и зависимость режима браузера / документа для последнего.

Селектор table:nth-of-type(2) tr, даже если он поддерживается, здесь не подходит.

Значение элемента (Год постройки), который меня интересует, не всегда совпадает с одним и тем же индексом, поэтому использование индекса здесь неправильная идея

На основании более тщательного изучения вашего кода кажется, чтоизменчивость, которую вы пытаетесь учесть, - это потенциальная разница в количестве столбцов в целевой таблице и, следовательно, вероятность того, что ваш элемент будет находиться в td с другим индексом в данной строке (вы не пытаетесь учестьизменчивость строк например ...). Таким образом, мы ищем разные отношения в целом;не требуется взаимосвязь между элементами;или динамически определять соответствующий индекс;или, может быть, даже их комбинация.

ИМО:

  • Тот же URI, но альтернативный элемент на странице с более коротким, надеюсь, более надежным селектором;
  • Другой URI XHR, где желаемый элемент связан с более надежным селектором, например, уникальным идентификатором;
  • тег script с хорошей цепочкой для регулярных выражений (var yearBuilt = 1234;);
  • Позиционная стратегия, которая имеет меньше зависимостей и / или, исходя из опыта, более высокую вероятность стабильности во времени

Кроме того,

  • Оптимизирована дляболее быстрый поиск

Я понимаю, что вышеизложенное является перефразировкой одной и той же общей идеи.

Глядя на соображения и две предоставленные ссылки:

Годbuild, связанный с MAIN AREA, присутствует только в одном месте в документе. Примечание: я сохраняю предположение, что это следующая строка ниже соответствующей строки заголовка. Я не изучил достаточно ссылок, чтобы узнать, может ли значение этого года варьироваться в зависимости от области собственности, и вы не указали, что является обязательным. * В этом примере MAIN AREA является первой частью, перечисленной с датой сборки.

Похоже, что страница не извлекает требуемое содержимое из дополнительных запросов, поэтому альтернативный источник не сразу очевиден. Похоже, что нет выделенного публичного API. функция поиска не предоставляет необходимой информации из своих запросов POST, а загружаемые файлы имеют задержку 3-4 месяца, в основном .txt и не предоставляют никаких реальных возможностей дляболее быстрая идентификация требуемой информации (на самом деле это было бы намного более трудоемким и менее надежным).

Это оставляет рассмотрение 4. Вам нужен способ найти правильный столбец в правой таблице. HTML имеет очень повторяющуюся структуру с несколькими хорошими «крючками». Вместо того, чтобы генерировать более хрупкий путь, зависящий от связей таблиц, вы разумно выбрали цикл за tr с (поэтому он должен быть в таблице), ища строку заголовка ключа в tr innerText. Таким образом, исключается риск появления строки заголовка в другом столбце и / или другой таблице для более короткого пути прохождения и гибкости перехода к следующей строке, которая, как предполагается, содержит интересующие данные.

Пока что я думаю, что хороший выбор, хотя я лично предпочел бы ограничить поиск заголовками (th), а затем перейти к родителю. Дополнительным преимуществом здесь является то, что я мог бы смягчить для вашей следующей части:

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

Здесь вы ввели ненужное предположение / риск, что ваша колонка интересов всегда будет предпоследней. Хотя вы могли бы зациклить все заголовки и перейти к родительскому узлу, я рискнул бы ограничиться соответствующей таблицей путем поиска уникальной строки в заголовке панели и затем захватить таблицу next-sibling перед проверкой заголовков. Он вводит IMO разумное предположение относительно отношения panel heading к table и panel контента. Это тогда позволяет нам найти правильный индекс для заголовка на основе 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) 'вашей версии
...