Использовать 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+