Использовать getElementById для HTMLElement вместо HTMLDocument
Я играл со скребками данных с веб-страниц, используя VBS/VBA.
Если бы это был Javascript, я бы отсутствовал, так как это было легко, но в VBS/VBA это не так прямолинейно.
Это пример, который я сделал для ответа, он работает, но я планировал доступ к дочерним узлам с помощью getElementByTagName
, но я не мог понять, как их использовать! Объект HTMLElement
не имеет этих методов.
Sub Scrape()
Dim Browser As InternetExplorer
Dim Document As HTMLDocument
Dim Elements As IHTMLElementCollection
Dim Element As IHTMLElement
Set Browser = New InternetExplorer
Browser.navigate "http://www.hsbc.com/about-hsbc/leadership"
Do While Browser.Busy And Not Browser.readyState = READYSTATE_COMPLETE
DoEvents
Loop
Set Document = Browser.Document
Set Elements = Document.getElementsByClassName("profile-col1")
For Each Element in Elements
Debug.Print "[ name] " & Trim(Element.Children(1).Children(0).innerText)
Debug.Print "[ title] " & Trim(Element.Children(1).Children(1).innerText)
Next Element
Set Document = Nothing
Set Browser = Nothing
End Sub
Я смотрю на свойство HTMLElement.document
, видя, что это похоже на фрагмент документа, но с его трудностью работать или просто не то, что я думаю
Dim Fragment As HTMLDocument
Set Element = Document.getElementById("example") ' This works
Set Fragment = Element.document ' This doesn't
Это также кажется длинным способом для этого (хотя, как правило, это путь для vba imo).
Кто-нибудь знает, есть ли более простой способ цепочки функций?
Document.getElementById("target").getElementsByTagName("tr")
будет потрясающе...
Ответы
Ответ 1
Мне тоже это не нравится.
Итак, используйте javascript:
Public Function GetJavaScriptResult(doc as HTMLDocument, jsString As String) As String
Dim el As IHTMLElement
Dim nd As HTMLDOMTextNode
Set el = doc.createElement("INPUT")
Do
el.ID = GenerateRandomAlphaString(100)
Loop Until Document.getElementById(el.ID) Is Nothing
el.Style.display = "none"
Set nd = Document.appendChild(el)
doc.parentWindow.ExecScript "document.getElementById('" & el.ID & "').value = " & jsString
GetJavaScriptResult = Document.getElementById(el.ID).Value
Document.removeChild nd
End Function
Function GenerateRandomAlphaString(Length As Long) As String
Dim i As Long
Dim Result As String
Randomize Timer
For i = 1 To Length
Result = Result & Chr(Int(Rnd(Timer) * 26 + 65 + Round(Rnd(Timer)) * 32))
Next i
GenerateRandomAlphaString = Result
End Function
Сообщите мне, если у вас есть проблемы с этим; Я изменил контекст от метода к функции.
Кстати, какую версию IE вы используете? Я подозреваю, IE8. Если вы перейдете на IE8, я предполагаю, что он обновит shdocvw.dll до ieframe.dll, и вы сможете использовать document.querySelector/All.
Edit
Комментарий комментария, который на самом деле не является комментарием:
В основном способ сделать это в VBA - это перемещение дочерних узлов. Проблема в том, что вы не получаете правильные возвращаемые типы. Вы можете исправить это, создав собственные классы, которые (отдельно) реализуют IHTMLElement и IHTMLElementCollection; но для этого слишком много боли для меня, чтобы сделать это, не получая деньги:). Если вы решились, перейдите и прочитайте ключевое слово "Реализации" для VB6/VBA.
Public Function getSubElementsByTagName(el As IHTMLElement, tagname As String) As Collection
Dim descendants As New Collection
Dim results As New Collection
Dim i As Long
getDescendants el, descendants
For i = 1 To descendants.Count
If descendants(i).tagname = tagname Then
results.Add descendants(i)
End If
Next i
getSubElementsByTagName = results
End Function
Public Function getDescendants(nd As IHTMLElement, ByRef descendants As Collection)
Dim i As Long
descendants.Add nd
For i = 1 To nd.Children.Length
getDescendants nd.Children.Item(i), descendants
Next i
End Function
Ответ 2
Sub Scrape()
Dim Browser As InternetExplorer
Dim Document As htmlDocument
Dim Elements As IHTMLElementCollection
Dim Element As IHTMLElement
Set Browser = New InternetExplorer
Browser.Visible = True
Browser.navigate "http://www.stackoverflow.com"
Do While Browser.Busy And Not Browser.readyState = READYSTATE_COMPLETE
DoEvents
Loop
Set Document = Browser.Document
Set Elements = Document.getElementById("hmenus").getElementsByTagName("li")
For Each Element In Elements
Debug.Print Element.innerText
'Questions
'Tags
'Users
'Badges
'Unanswered
'Ask Question
Next Element
Set Document = Nothing
Set Browser = Nothing
End Sub
Ответ 3
Спасибо за ответ выше с подпрограммой Scrape(). Код работал отлично, как написано, и я смог затем преобразовать код для работы с конкретным сайтом, который я пытаюсь очистить.
У меня недостаточно репутации для продвижения или комментариев, но у меня действительно есть некоторые незначительные улучшения, чтобы добавить ответ:
-
Вам нужно будет добавить ссылку VBA через "Инструменты\Ссылки" в "Библиотека объектов Microsoft HTML для компиляции кода.
-
Я прокомментировал строку Browser.Visible и добавил комментарий следующим образом
'if you need to debug the browser page, uncomment this line:
'Browser.Visible = True
-
И я добавил строку, чтобы закрыть браузер перед установкой браузера = ничего:
Browser.Quit
Еще раз спасибо!
ETA: это работает на машинах с IE9, но не с машинами с IE8. У кого-нибудь есть исправление?
Нашел исправление, поэтому вернулся сюда, чтобы опубликовать его. Функция ClassName доступна в IE9. Чтобы это работало в IE8, вы используете querySelectorAll с точкой, предшествующей имени класса объекта, который вы ищете:
'Set repList = doc.getElementsByClassName("reportList") 'only works in IE9, not in IE8
Set repList = doc.querySelectorAll(".reportList") 'this works in IE8+