web-dev-qa-db-fra.com

Utilisez getElementById sur HTMLElement au lieu de HTMLDocument

Je me suis amusé à extraire des données de pages Web à l'aide de VBS/VBA.

Si c'était Javascript, je serais absent aussi facilement, mais cela ne semble pas être aussi simple dans VBS/VBA.

C’est un exemple que j’ai fait pour trouver une réponse, cela fonctionne mais j’avais prévu d’accéder aux nœuds enfants à l’aide de getElementByTagName mais je ne savais pas comment les utiliser! L'objet HTMLElement n'a pas ces méthodes.

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

J'examinais la propriété HTMLElement.document pour voir s'il s'agissait d'un fragment du document, mais il est difficile de travailler avec ou tout simplement pas ce que je pense.

Dim Fragment As HTMLDocument
Set Element = Document.getElementById("example") ' This works
Set Fragment = Element.document ' This doesn't

Cela semble aussi un moyen long pour le faire (bien que ce soit généralement le chemin pour vba imo). Quelqu'un sait s'il existe un moyen plus simple de chaîner des fonctions?

Document.getElementById("target").getElementsByTagName("tr") serait génial ...

12
NickSlash

Je n'aime pas ça non plus.

Donc utilisez 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

Faites-moi savoir si vous avez des problèmes avec cela; J'ai changé le contexte d'une méthode à une fonction.

Au fait, quelle version de IE utilisez-vous? Je suppose que vous êtes sur <IE8. Si vous passez à IE8, je présume que shdocvw.dll sera mis à jour en ieframe.dll et vous pourrez utiliser document.querySelector/All.

Modifier

Réponse à un commentaire qui n'est pas vraiment un commentaire: En gros, le moyen de le faire dans VBA consiste à traverser les nœuds enfants. Le problème est que vous n'obtenez pas les types de retour corrects. Vous pouvez résoudre ce problème en faisant vos propres classes qui implémentent (séparément) IHTMLElement et IHTMLElementCollection; mais c'est beaucoup trop pénible pour moi de le faire sans être payé :). Si vous êtes déterminé, consultez le mot-clé Implements pour 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
4
mkingston
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
12
dee

Merci à dee pour la réponse ci-dessus avec le sous-programme Scrape (). Le code fonctionnait parfaitement tel quel, et j'ai ensuite pu convertir le code pour qu'il fonctionne avec le site Web spécifique que je tente de gratter.

Je n'ai pas assez de réputation pour faire voter ou commenter, mais j'ai quelques améliorations mineures à ajouter à la réponse de dee:

  1. Pour que le code soit compilé, vous devez ajouter la référence VBA via "Outils\Références" à "Bibliothèque d'objets Microsoft HTML.".

  2. J'ai commenté la ligne Browser.Visible et ajouté le commentaire comme suit

    'if you need to debug the browser page, uncomment this line:
    'Browser.Visible = True
    
  3. Et j'ai ajouté une ligne pour fermer le navigateur avant Set Browser = Nothing:

    Browser.Quit
    

Merci encore dee!

ETA: cela fonctionne sur les machines avec IE9, mais pas avec IE8. Quelqu'un a un problème?

J'ai trouvé le correctif moi-même, alors je suis revenu ici pour le poster. La fonction ClassName est disponible dans IE9. Pour que cela fonctionne dans IE8, vous utilisez querySelectorAll, avec un point précédant le nom de la classe de l'objet que vous recherchez:

'Set repList = doc.getElementsByClassName("reportList") 'only works in IE9, not in IE8
Set repList = doc.querySelectorAll(".reportList")       'this works in IE8+
0
WizzleWuzzle

J'utiliserais la requête XMLHTTP pour récupérer le contenu de la page beaucoup plus rapidement. Ensuite, il est assez facile d'utiliser querySelectorAll pour appliquer un sélecteur de classe CSS à saisir par nom de classe. Ensuite, vous accédez aux éléments enfants par nom de balise et index.

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

Références:

VBE> Outils> Références> Bibliothèque d'objets Microsoft HTML

0
QHarr