Мой текущий проект состоит из извлечения данных из исходного кода 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 или код должен уметь находить каждый идентификатор, независимо от того, в каком «потомке» он находится?
Большое спасибо
В значительной степени да. Мне не нужны гиперссылки на XML и прочее, но в основном мне нужна вся информация в таблицах. В конце я хотел бы иметь CSV или заполненный лист Excel, который показывает все данные для каждого случая со свойством, что те же категории находятся в одном столбце. Мне тоже картинки не нужны.






То, что вы спрашиваете, является довольно большой просьбой, поэтому я дам несколько указателей и начальный код. Мой код должен записывать все таблицы, но вы захотите поиграть, чтобы получить желаемый формат. Определенно, есть достаточно логики для эффективного выбора элементов, чтобы это могло помочь. * Я не тестировал использование класса для циклического перебора всех извлеченных идентификаторов из-за временных ограничений, но тестировал индивидуальный случай и получение всех идентификаторов.
Чтобы получить исходные ссылки и идентификаторы кейсов:
Я мог бы использовать функцию, возвращающую массив, содержащий ссылки и идентификаторы. Если вы извлечете идентификаторы, им можно будет передать запрос 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") без ошибки.
Какое сообщение об ошибке, пожалуйста? Я только что проверил первые две ссылки, и они работают. Просто раздражает, что таблицы полностью перечислены одна под другой. Вот где использование xpath может быть более полезным для определения более согласованного макета на странице, но потребует больше размышлений, чем я могу дать в настоящее время.
Код ошибки - ошибка времени выполнения «-2146697211 (800c0005)». stackoverflow.com/questions/11726661/… Поменять "Get" на "Post" не помогло. Кстати. Я использую XML v6.0. Большое спасибо за ваши усилия!
Может даже быть кто-то, кто будет преобразовывать xml, который мог бы написать что-то приятное, которое вы могли бы вызвать в качестве подпрограммы в цикле, чтобы помочь с извлечением в формате, позволяющем писать через страницу.
Да, он прекращается, как только я приезжаю в первый раз. У меня была такая же проблема с пробным кодом на сайте вашей первой ссылки codingislove.com/http-requests-excel-vba.
Затем я получаю сообщение об ошибке времени выполнения «-2147012889 (80072ee7)». После «.Open« GET », URL, False» мой объект http все еще имеет «неизвестную ошибку» для большинства его переменных, включая responseText.
Вы сказали, что тоже пробовали кодировать пример любви и ту же проблему? Мне интересно, применяются ли какие-то настройки безопасности.
Да, вот этот: 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) Безопасность, вероятно, применяется, поскольку я использую это на работе, что также мешает мне делать это с любым другим языком программирования или инструментом.
Будет ли у вас возможность протестировать на другой машине? В противном случае, как я уже сказал, вы можете легко переделать вышеуказанное, чтобы использовать Internet Explorer, только если он может перемещаться по страницам. Я могу быстро набросать кое-что для вас сейчас.
Я проверю его, как только вернусь с работы, и если он работает, вероятно, это как-то связано с настройками безопасности здесь. Что именно происходит, когда вы вызываете функцию .send, поскольку она может иметь отношение к настройкам безопасности? Пожалуйста, не тратьте слишком много времени на мои проблемы. Вы уже оказали мне большую услугу, и меня всегда впечатляло то, как люди вроде вас на самом деле понимают программирование, а не просто используют его так, как я люблю исправления.
.Send делает то, что он говорит ... он пытается полагаться на ваш запрос GET к целевой службе API.
Я почти уверен, что вы дали мне достаточно советов, чтобы я смог найти способ достичь своей цели. Возможно, это не самый лучший способ, но это хорошее начало для достижения результата. Еще раз спасибо!
Не беспокойся. Сообщите мне, если возникнут дополнительные вопросы.
Что именно вам нужно? Все на странице?