12

Вопрос по web-scraping, vba – Используйте getElementById на HTMLElement вместо HTMLDocument

я играл с данными из веб-страниц, используя VBS / VBA.

Если бы это был Javascript I 'Я буду далеко, как легко, но это не такВ 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") было бы здорово ...

4ответа

12

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
4

Я неТоже не нравится.

Так что используйте 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.

редактировать

Комментарий ответ, который не 'На самом деле комментарий: в основном, способ сделать это в VBA - пройти через дочерние узлы. Проблема в том, что ты неполучить правильные типы возврата. Вы можете исправить это, создав собственные классы, которые (отдельно) реализуют IHTMLElement и IHTMLElementCollection; но это's Слишком много боли для меня, чтобы сделать это без оплаты :). Если ты'определитесь, перейдите и прочитайте ключевое слово Implements для VB6 / VBA.I '

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
0

Я бы использовал запрос XMLHTTP, чтобы получить содержимое страницы как можно быстрее. Тогда достаточно просто использовать querySelectorAll, чтобы применить селектор класса CSS для захвата по имени класса. Затем вы получаете доступ к дочерним элементам по имени тега и индексу.

Option Explicit
Public Sub GetInfo()
    Dim sResponse As String, html As HTMLDocument, elements As Object, i As Long

    With CreateObject("MSXML2.XMLHTTP")
        .Open "GET", "https://www.hsbc.com/about-hsbc/leadership", False
        .setRequestHeader "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"
        .send
        sResponse = StrConv(.responseBody, vbUnicode)
    End With
    Set html = New HTMLDocument
    With html
        .body.innerHTML = sResponse
        Set elements = .querySelectorAll(".profile-col1")
        For i = 0 To elements.Length - 1
            Debug.Print String(20, Chr$(61))
            Debug.Print elements.item(i).getElementsByTagName("a")(0).innerText
            Debug.Print elements.item(i).getElementsByTagName("p")(0).innerText
            Debug.Print elements.item(i).getElementsByTagName("p")(1).innerText
        Next
    End With
End Sub

Рекомендации:

VBE> Инструменты> Отзывы> Microsoft HTML Object Library

0

Спасибо dee за ответ выше с подпрограммой Scrape (). Код работал отлично, как написано, и я смог преобразовать код для работы с конкретным сайтом, который я пытаюсь очистить.

У меня недостаточно репутации, чтобы высказывать свое мнение или комментировать, но у меня действительно есть небольшие улучшения, которые можно добавить кответ:

  1. Вам нужно будет добавить ссылку на VBA через "Инструменты \ Ссылка» к "Microsoft HTML Object Library для компиляции кода.

  2. Я закомментировал строку Browser.Visible и добавил комментарий следующим образом

    'if you need to debug the browser page, uncomment this line:
    'Browser.Visible = True
    
  3. И я добавил строку, чтобы закрыть браузер до Set Browser = Nothing:

    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+

RelatedQuestions