VBA не может получать данные из HTML с помощью .getElementsByTag () или .getElementByID ()

Мой текущий проект состоит из извлечения данных из исходного кода HTML. В частности, я просматриваю случаи сбоев на этом веб-сайте:

https://crashviewer.nhtsa.dot.gov/nass-cds/CaseForm.aspx?xsl=main.xsl&CaseID=112007272

Я хотел бы собрать все соответствующие данные из HTML, ища .innertext с конкретными тегами / идентификаторами.

Мой код на данный момент:

Sub ExtractData()

mystart:

'First I create two Internet Explorer object

Set objIE = CreateObject("InternetExplorer.Application")      'this browser contains the list of cases
objIE.Top = 0
objIE.Left = 0
objIE.Width = 1600
objIE.Height = 900
objIE.Visible = True 'We can see IE

Set objIEdata = CreateObject("InternetExplorer.Application")    'this browser opens the specific case
objIEdata.Top = 0
objIEdata.Left = 0
objIEdata.Width = 1600
objIEdata.Height = 900
objIEdata.Visible = True 'We can see IE

On Error Resume Next
objIE.navigate ("https://crashviewer.nhtsa.dot.gov/LegacyCDS/Index")        'url of website

Do
    DoEvents
    If Err.Number <> 0 Then
        objIE.Quit
        Set objIE = Nothing
        GoTo mystart:
    End If
Loop Until objIE.readystate = 4

'we define an object variable Alllinks and loop through all the links to search for

Set aAlllinks = objIE.document.getElementsByTagName("button")                'looks for Search Button 
For Each Hyperlink In aAlllinks
    If Hyperlink.innertext = " Search" Then
        Hyperlink.Click
        Exit For
    Else
        MsgBox "Search Button was not found. Please improve code!"
    End If

Next

Application.Wait (Now + TimeValue("0:00:02"))

Set bAlllinks = objIE.document.getElementsByTagName("a")                     'all Hyperlinks on webpage start with Tag "a"
For Each Hyperlink In bAlllinks
    If UBound(Split(Hyperlink.innertext, "-")) = 2 And Len(Hyperlink.innertext) = 11 Then             'case specific to find the Hyperlinks which contain cases
        Debug.Print Hyperlink.innertext

        '2nd IE is used for each case

restart:
            objIEdata.navigate (Hyperlink.href)        'url of each case

            Do
                DoEvents
                If Err.Number <> 0 Then
                    objIEdata.Quit
                    Set objIE = Nothing
                    GoTo restart:
                End If
            Loop Until objIEdata.readystate = 4

            Set register = objIEdata.document.getElementByTagName("tbody")             'objIEdata.document.getElementByID("main").getElementByID("mainSection")  '.getElementByID("bodyMain").getElementsByTagName("tbody")
            For Each untermenue In register
                Debug.Print untermenue.innerHTML
            Next

            Application.Wait (Now + TimeValue("0:00:02"))




    End If
Next




objIE.Quit
objIEdata.Quit

End Sub

Обратите внимание, что IE доступен только для целей отладки.

Меня смущает

Set register = objIEdata.document.getElementByTagName("tbody").

Если я ищу .TagName("tbody"), регистр переменной возвращается пустым, и то же самое происходит, если я ищу .ID("bodyMain"). К сожалению, я не знаком с HTML и тем, как VBA взаимодействует с HTML-документом. У меня создалось впечатление, что я могу обращаться ко всем элементам по их идентификатору, если он у них есть, но это, похоже, не работает.

Нужно ли мне самому работать с «ветвями» HTML или код должен уметь находить каждый идентификатор, независимо от того, в каком «потомке» он находится?

Большое спасибо

Что именно вам нужно? Все на странице?

QHarr 30.11.2018 11:59

В значительной степени да. Мне не нужны гиперссылки на XML и прочее, но в основном мне нужна вся информация в таблицах. В конце я хотел бы иметь CSV или заполненный лист Excel, который показывает все данные для каждого случая со свойством, что те же категории находятся в одном столбце. Мне тоже картинки не нужны.

Noco 30.11.2018 13:03
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Введение в CSS
Введение в CSS
CSS является неотъемлемой частью трех основных составляющих front-end веб-разработки.
Как выровнять Div по центру?
Как выровнять Div по центру?
Чтобы выровнять элемент <div>по горизонтали и вертикали с помощью CSS, можно использовать комбинацию свойств и значений CSS. Вот несколько методов,...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Travel Booking Angular Template один из лучших Travel & Tour booking template in the world. 30+ валидированных HTML5 страниц, которые помогут...
0
2
418
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

То, что вы спрашиваете, является довольно большой просьбой, поэтому я дам несколько указателей и начальный код. Мой код должен записывать все таблицы, но вы захотите поиграть, чтобы получить желаемый формат. Определенно, есть достаточно логики для эффективного выбора элементов, чтобы это могло помочь. * Я не тестировал использование класса для циклического перебора всех извлеченных идентификаторов из-за временных ограничений, но тестировал индивидуальный случай и получение всех идентификаторов.


Чтобы получить исходные ссылки и идентификаторы кейсов:

Я мог бы использовать функцию, возвращающую массив, содержащий ссылки и идентификаторы. Если вы извлечете идентификаторы, им можно будет передать запрос XMLHTTP, который я показываю ниже.

URL: https://crashviewer.nhtsa.dot.gov/LegacyCDS/Search

Public Function GetLinksAndIds(ByVal URL) As Variant
    Dim ie As InternetExplorer, i As Long
    Set ie = New InternetExplorer
    With ie
        .Visible = True
        .navigate2 URL

        While .Busy Or .readyState < 4: DoEvents: Wend

        .document.getElementById("btnSubmit1").Click

         While .Busy Or .readyState < 4: DoEvents: Wend

        Dim caseLinks As Object, id As String, newURL As String
        Set caseLinks = .document.querySelectorAll("[href*='CaseID=']")

        Dim linksAndIds()
        ReDim linksAndIds(1 To caseLinks.Length, 1 To 2)
        For i = 0 To caseLinks.Length - 1
           linksAndIds(i + 1, 1) = caseLinks.item(i)
           linksAndIds(i + 1, 2) = Replace$(caseLinks.item(i), "https://crashviewer.nhtsa.dot.gov/nass-cds/CaseForm.aspx?xsl=main.xsl&CaseID = ", vbNullString)
        Next

        .Quit
    End With
    GetLinksAndIds = linksAndIds
End Function

Пример возвращаемых значений:


Для каждого случая - с использованием XMLHTTP:

У меня возникнет соблазн избежать IE и использовать XMLHTTP запрос (строка запроса в кодировке url, возвращающая более читаемую версию страницы с использованием опции печати). Хотя я проанализировал с помощью селекторов css, вы можете прочитать ответ в MSXML2.DOMDocument60 и запросить, например, с помощью XPath. Вы можете объединить caseid в URL.

Option Explicit
Public Sub GetTables()
    Dim sResponse As String, html As HTMLDocument, clipboard As Object, ws As Worksheet
    Set ws = ThisWorkbook.Worksheets("Sheet1")
    Set clipboard = GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")

    With CreateObject("MSXML2.XMLHTTP")
        .Open "GET", "https://crashviewer.nhtsa.dot.gov/nass-cds/CaseForm.aspx?ViewPage&xsl=Case.xsl&tab=Crash&form=CaseForm&baseNode=&vehnum=-1&occnum=-1&pos=-1&pos2=-1&websrc=true&title=Crash%20Overview%20-%20Summary&caseid=112007272&year=&fullimage=false", False '<==concatenate caseid into URL
        .setRequestHeader "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"
        .send
        sResponse = .responseText
    End With

    Set html = New HTMLDocument
    html.body.innerHTML = sResponse
    Dim tables As Object, i As Long
    Set tables = html.querySelectorAll("table")
    For i = 0 To tables.Length - 1
        clipboard.SetText tables.item(i).outerHTML
        clipboard.PutInClipboard
        ws.Cells(LastRow(ws) + 2, 1).PasteSpecial
    Next
End Sub

'https://www.rondebruin.nl/win/s9/win005.htm '<< Function below modified from here

Public Function LastRow(ByVal sh As Worksheet) As Long
    On Error Resume Next
    LastRow = sh.Cells.Find(What: = "*", _
                            After:=sh.Range("A1"), _
                            Lookat:=xlPart, _
                            LookIn:=xlFormulas, _
                            SearchOrder:=xlByRows, _
                            SearchDirection:=xlPrevious, _
                            MatchCase:=False).Row
    On Error GoTo 0
End Function

Как это может выглядеть в целом (не проверено) с использованием класса для хранения объекта xmlhttp:

Класс clsHTTP:

Option Explicit

Private http As Object

Private Sub Class_Initialize()
    Set http = CreateObject("MSXML2.XMLHTTP")
End Sub

Public Function GetString(ByVal URL As String) As String
    Dim sResponse As String
    With http
        .Open "GET", URL, False
        .send
        sResponse = .responseText
    End With
End Function

Стандартный модуль 1:

Option Explicit
Public Sub GetTables()
    Dim sResponse As String, html As HTMLDocument, clipboard As Object, ws As Worksheet
    Dim initialLinksURL As String, http As clsHTTP, i As Long, j As Long, newURL As String
    Set http = New clsHTTP
    Set ws = ThisWorkbook.Worksheets("Sheet1")
    Set clipboard = GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
    Set html = New HTMLDocument
    initialLinksURL = "https://crashviewer.nhtsa.dot.gov/LegacyCDS/Search"

    Dim linksAndIds()
    linksAndIds = GetLinksAndIds(initialLinksURL)

    For i = LBound(linksAndIds, 2) To UBound(linksAndIds, 2)

        newURL = "https://crashviewer.nhtsa.dot.gov/nass-cds/CaseForm.aspx?ViewPage&xsl=Case.xsl&tab=Crash&form=CaseForm&baseNode=&vehnum=-1&occnum=-1&pos=-1&pos2=-1&websrc=true&title=Crash%20Overview%20-%20Summary&caseid = " & linksAndIds(i, 2) & "&year=&fullimage=false"
        html.body.innerHTML = http.GetString(newURL)
        Dim tables As Object

        Set tables = html.querySelectorAll("table")

        For j = 0 To tables.Length - 1
            clipboard.SetText tables.item(j).outerHTML
            clipboard.PutInClipboard
            ws.Cells(LastRow(ws) + 2, 1).PasteSpecial
        Next
    Next
End Sub

'https://www.rondebruin.nl/win/s9/win005.htm

Public Function LastRow(ByVal sh As Worksheet) As Long
    On Error Resume Next
    LastRow = sh.Cells.Find(What: = "*", _
                            After:=sh.Range("A1"), _
                            Lookat:=xlPart, _
                            LookIn:=xlFormulas, _
                            SearchOrder:=xlByRows, _
                            SearchDirection:=xlPrevious, _
                            MatchCase:=False).Row
    On Error GoTo 0
End Function

Public Function GetLinksAndIds(ByVal URL) As Variant
    Dim ie As InternetExplorer, i As Long
    Set ie = New InternetExplorer
    With ie
        .Visible = True
        .navigate URL

        While .Busy Or .readyState < 4: DoEvents: Wend

        .document.getElementById("btnSubmit1").Click

         While .Busy Or .readyState < 4: DoEvents: Wend

        Dim caseLinks As Object, id As String, newURL As String
        Set caseLinks = .document.querySelectorAll("[href*='CaseID=']")

        Dim linksAndIds()
        ReDim linksAndIds(1 To caseLinks.Length, 1 To 2)
        For i = 0 To caseLinks.Length - 1
           linksAndIds(i + 1, 1) = caseLinks.item(i)
           linksAndIds(i + 1, 2) = Replace$(caseLinks.item(i), "https://crashviewer.nhtsa.dot.gov/nass-cds/CaseForm.aspx?xsl=main.xsl&CaseID = ", vbNullString)
        Next

        .Quit
    End With
    GetLinksAndIds = linksAndIds
End Function

Все варианты Internet Explorer:

Option Explicit

Public Sub GetTables()
    Dim sResponse As String, html As HTMLDocument, clipboard As Object, ws As Worksheet
    Dim initialLinksURL As String, i As Long, j As Long, newURL As String
    Set ws = ThisWorkbook.Worksheets("Sheet1")
    Set clipboard = GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
    Set html = New HTMLDocument
    initialLinksURL = "https://crashviewer.nhtsa.dot.gov/LegacyCDS/Search"

    Dim ie As InternetExplorer, caseLinks As Object
    Set ie = New InternetExplorer
    With ie
        .Visible = True
        .Navigate2 initialLinksURL

        While .Busy Or .readyState < 4: DoEvents: Wend

        .document.getElementById("btnSubmit1").Click

        While .Busy Or .readyState < 4: DoEvents: Wend

        Set caseLinks = .document.querySelectorAll("[href*='CaseID=']")

        Dim linksAndIds()
        ReDim linksAndIds(1 To caseLinks.Length, 1 To 2)
        For i = 0 To caseLinks.Length - 1
            linksAndIds(i + 1, 1) = caseLinks.item(i)
            linksAndIds(i + 1, 2) = Replace$(caseLinks.item(i), "https://crashviewer.nhtsa.dot.gov/nass-cds/CaseForm.aspx?xsl=main.xsl&CaseID = ", vbNullString)
        Next

        For i = LBound(linksAndIds, 2) To 2      ' UBound(linksAndIds, 2)

            newURL = "https://crashviewer.nhtsa.dot.gov/nass-cds/CaseForm.aspx?ViewPage&xsl=Case.xsl&tab=Crash&form=CaseForm&baseNode=&vehnum=-1&occnum=-1&pos=-1&pos2=-1&websrc=true&title=Crash%20Overview%20-%20Summary&caseid = " & linksAndIds(i, 2) & "&year=&fullimage=false"
            .Navigate2 newURL

            While .Busy Or .readyState < 4: DoEvents: Wend

            Dim tables As Object

            Set tables = .document.querySelectorAll("table")

            For j = 0 To tables.Length - 1
                clipboard.SetText tables.item(j).outerHTML
                clipboard.PutInClipboard
                ws.Cells(LastRow(ws) + 2, 1).PasteSpecial
            Next
        Next

        .Quit
    End With
End Sub

'https://www.rondebruin.nl/win/s9/win005.htm

Public Function LastRow(ByVal sh As Worksheet) As Long
    On Error Resume Next
    LastRow = sh.Cells.Find(What: = "*", _
                            After:=sh.Range("A1"), _
                            Lookat:=xlPart, _
                            LookIn:=xlFormulas, _
                            SearchOrder:=xlByRows, _
                            SearchDirection:=xlPrevious, _
                            MatchCase:=False).Row
    On Error GoTo 0
End Function

Спасибо. Это кажется более элегантным способом решения моей проблемы. К сожалению, я не могу пройти .send в CreateObject ("MSXML2.XMLHTTP") без ошибки.

Noco 30.11.2018 12:52

Какое сообщение об ошибке, пожалуйста? Я только что проверил первые две ссылки, и они работают. Просто раздражает, что таблицы полностью перечислены одна под другой. Вот где использование xpath может быть более полезным для определения более согласованного макета на странице, но потребует больше размышлений, чем я могу дать в настоящее время.

QHarr 30.11.2018 13:29

Код ошибки - ошибка времени выполнения «-2146697211 (800c0005)». stackoverflow.com/questions/11726661/… Поменять "Get" на "Post" не помогло. Кстати. Я использую XML v6.0. Большое спасибо за ваши усилия!

Noco 30.11.2018 13:33

Может даже быть кто-то, кто будет преобразовывать xml, который мог бы написать что-то приятное, которое вы могли бы вызвать в качестве подпрограммы в цикле, чтобы помочь с извлечением в формате, позволяющем писать через страницу.

QHarr 30.11.2018 13:33

Да, он прекращается, как только я приезжаю в первый раз. У меня была такая же проблема с пробным кодом на сайте вашей первой ссылки codingislove.com/http-requests-excel-vba.

Noco 30.11.2018 13:39

Затем я получаю сообщение об ошибке времени выполнения «-2147012889 (80072ee7)». После «.Open« GET », URL, False» мой объект http все еще имеет «неизвестную ошибку» для большинства его переменных, включая responseText.

Noco 30.11.2018 13:48

Вы сказали, что тоже пробовали кодировать пример любви и ту же проблему? Мне интересно, применяются ли какие-то настройки безопасности.

QHarr 30.11.2018 13:49

Да, вот этот: Dim xmlhttp As New MSXML2.XMLHTTP60, myurl As String myurl = "requestb.in/15oxrjh1" // замените на свой URL 3 xmlhttp.Open "GET", myurl, False xmlhttp.Send MsgBox (xmlhttp.responseText) Безопасность, вероятно, применяется, поскольку я использую это на работе, что также мешает мне делать это с любым другим языком программирования или инструментом.

Noco 30.11.2018 13:51

Будет ли у вас возможность протестировать на другой машине? В противном случае, как я уже сказал, вы можете легко переделать вышеуказанное, чтобы использовать Internet Explorer, только если он может перемещаться по страницам. Я могу быстро набросать кое-что для вас сейчас.

QHarr 30.11.2018 13:55

Я проверю его, как только вернусь с работы, и если он работает, вероятно, это как-то связано с настройками безопасности здесь. Что именно происходит, когда вы вызываете функцию .send, поскольку она может иметь отношение к настройкам безопасности? Пожалуйста, не тратьте слишком много времени на мои проблемы. Вы уже оказали мне большую услугу, и меня всегда впечатляло то, как люди вроде вас на самом деле понимают программирование, а не просто используют его так, как я люблю исправления.

Noco 30.11.2018 14:02

.Send делает то, что он говорит ... он пытается полагаться на ваш запрос GET к целевой службе API.

QHarr 30.11.2018 14:04

Я почти уверен, что вы дали мне достаточно советов, чтобы я смог найти способ достичь своей цели. Возможно, это не самый лучший способ, но это хорошее начало для достижения результата. Еще раз спасибо!

Noco 30.11.2018 14:08

Не беспокойся. Сообщите мне, если возникнут дополнительные вопросы.

QHarr 30.11.2018 14:09

Другие вопросы по теме