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 ...
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
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
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:
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.".
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
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+
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