Tutorial: Web-Scraping mit VBA – Teil 1

In dieser kleinen Tutorial-Serie will ich anhand einer Online-Handy-Datenbank zeigen, wie man mit VBA Seiten aus dem Internet abruft und nach Informationen sucht, die sich dann in einer Tabelle abspeichern lassen. Warum VBA? Es gibt vermutlich elegantere Lösungen, doch Excel ist eine Software, die die meisten zuhause nutzen. Man benötigt also keine zusätzliche Entwicklungsumgebung. Außerdem hat diese Methode den  Vorteil, dass die Daten sofort zur weiteren Verarbeitung verfügbar sind. VBA ist vielleicht nicht die performanteste Programmiersprache, dafür aber relativ leicht zu beherrschen.

(FYI: Rechtliches zum Thema Web- oder Screen-Scraping)

 

Diese Artikelserie richtet sich an den fortgeschrittenen Nutzer. Für den Einsteiger gehe ich nicht auf grundlegendes Programmierwissen ein (was sind Klassen, welche Variablen-Typen bietet Excel, etc.pp.) und für den professionellen Softwareentwickler sind meine Codebeispiele vermutlich zu infantil. Ich habe aber die Erfahrung gemacht, dass VBA und Excel für kurzfristige und kleine Projekte dieser Art recht nützliche Hilfsmittel sind. Außerdem bin ich kein „ausgebildeter Softwareentwickler“, weshalb ich an der Stelle auch gleich darauf Hinweise, dass Verbesserungsvorschläge sehr gerne gesehen sind!

Welches wissen solltest du also mitbringen?  Da es um das Parsen von HTML-Code geht, solltest du zumindest Bescheid wissen, wenn ich von div- und a-Elementen und css-Klassen rede. Du solltest auch wissen, wie man in VBA Variablen deklariert oder was eine if-Abfrage ist.

Das Endergebnis ist eine Tabelle mit technischen Spezifikationen zu den Mobiltelefonen, die inside-handy.de listet. Insgesamt werden drei Routinen genutzt, die – in umgekehrter Reihenfolge des Vorgehens – folgende Aufgabe haben:

Die letzte Routine greift auf eine Liste von URL zu, die auf die Datenblätter der Geräte verweisen. Von dort wird der HTML-Quellcode nach den  technischen Informationen durchsucht.

Auf inside-handy.de sind die Geräte nach Herstellern sortiert. Jede Herstellerseite verweist auf die entsprechenden Geräte. Wir werden also jede Herstellerseite (bzw. den entsprechenden HTML-Code) zunächst nach den URL zu den Geräten durchsuchen. Diese Aufgabe übernimmt die zweite Routine bzw. Prozedur.

Die erste Routine schließlich liefert die Liste aller URL zu den Herstellern, die wir uns aus dem HTML-Code der Herstellerübersicht auf inside-handy.de laden. Und damit geht es nun los:

1. Die URL zu den Hersteller-Unterseiten auslesen – sub getManufacturer

1.1 Datei per HTTP von einem Server laden

Im ersten Schritt laden wir die komplette HTML-Datei in den Zwischenspeicher. Dazu gibt es mindesten zwei Methoden, die gängigste ist vermutlich die über WinHttpRequest. Bevor du das nutzen kannst, musst du bei VBA unter Extras – Verweise jedoch erst die Microsoft HTML Object Library einbinden.

Dim url As String
Dim result As String
Dim winHttpReq As Object
url = "http://www.inside-handy.de/hersteller/handys"

Set winHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")
winHttpReq.Open "GET", url, False
winHttpReq.send
result = winHttpReq.responseText

Die Deklaration der Variablen erklärt sich von selbst. Nachdem ich eine Instanz vom WinHTTP-Objekt erzeugt habe, kann ich die Parameter übergeben. Dazu gehört neben der URL auch die Bestimmung des HTTP-Requests – nämlich GET. Der letzte, booleansche, Paramter gibt an, ob die Verbindung im asynchronen Modus geöffnet werden soll. Mit .send wird der Request tatsächlich ausgelöst und das Ergebni dann an die String-Variable result zurückgegeben. Dort befindet sich nun unser HTML-Code

Wir können unseren Request natürlich auch per POST absetzen und noch andere Header-Informationen anhängen:

Set winHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")

winHttpReq.Open = "Post", url, False
winHttpReq.setRequestHeader "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"
winHttpReq.setRequestHeader "Content-type", "application/x-www-form-urlencoded"
winHttpReq.send (URLEncode("username=user1&password=secret") )

result = winHttpReq.responseText

So ist es z.B. möglich, Formulardaten zu übermitteln, um an eine passwortgeschützte Seite oder die Ergebnisseite einer Suche zu gelangen. Dabei werden die POST-Daten als weiterer Parameter beim Senden mitgegeben. Eine weitere Möglichkeit ist die Durchführung einer HTTP-Authentifizierung:

Dim HTTPREQUEST_SETCREDENTIALS_FOR_SERVER As Boolean

winHttpReq.Open "GET", url, False
winHttpReq.SetCredentials "user", "password", HTTPREQUEST_SETCREDENTIALS_FOR_SERVER
winHttpReq.send

Neben dem winHttpRequest-Objekt gibt es noch eine weniger elegante Methoden, in dem direkt eine Instanz des Internet Explorers erzeugt wird:

Dim sPostData As String
Dim bPostData() As Byte
Dim WebBrowser: Set WebBrowser = CreateObject("InternetExplorer.Application")

WebBrowser.Visible = True

sPostData = URLEncode("username=user1&password=secret")
ReDim bPostData(Len(sPostData) - 1)
bPostData = StrConv(sPostDataData, vbFromUnicode)

WebBrowser.navigate url, 2 + 4 + 8, , bPostData, "Content-type: application/x-www-form-urlencoded"
Do While WebBrowser.Busy
    DoEvents
Loop
result = WebBrowser.document.body.innerHTML 
WebBrowser.Quit

Da diese Methode – wie gesagt – nicht sonderlich elegant ist, werde ich aber nicht weiter darauf eingehen.

Nun zurück zu unserem Skript. Den HTML-Code der Seite haben wir nun erstmal in einen String gelegt. Damit wir das HTML-Dokument bequem lesen können, erzeugen wir ein HTML-Document, an das wir den HTML-Code übergeben:

Set HTMLDoc = New HTMLDocument
Set HTMLDoc = CreateObject("htmlfile")
HTMLDoc.Open
HTMLDoc.write (CStr(result))
HTMLDoc.Close

Auch hier erklärt sich der Code fast von selber: Eine Instanz des Objektes erzeugen, diese Instanz zum „Befüllen“ vorbereiten, den String übergebne und die Instanz wieder „schließen“. Soweit, so unkompliziert. Im nächsten Schritt geht es nun direkt an das Parsen des Quellcodes um die Links zu den Herstellerseiten zu erhalten. Die Schleife dazu ist nicht sehr aufwendig:

    Dim oneElement1, allElements1 As IHTMLElementCollection
    Dim oneElement2, allElements2 As IHTMLElementCollection
    Dim oneElement3, allElements3 As IHTMLElementCollection
    
    Worksheets("srcURL").Range("a2").Select
    i = 0
    Set allElements1 = HTMLDoc.getElementsByTagName("a")
          
    i = 0
    For Each oneElement1 In allElements1
        If oneElement1.parentElement.className = "h_img" Then
            If oneElement1.parentElement.parentElement.ID = "h_alle" Then
                If InStr(1, oneElement1.getAttribute("href"), "/tablets") <= 0 Then
                    Selection.Offset(i, 0).Value = Replace(oneElement1.getAttribute("href"), "about:", "http://www.inside-handy.de")
                    i = i + 1
                End If
            End If
            
        End If    
    Next oneElement1
End Sub

Wie bin ich vorgegangen? Ich habe mir zunächst den Quellcode der Seite angeschaut. Die Liste der Hersteller ist dort eine Tabelle mit den Logos der Unternehmen. Die gewünschte Information befindet sich in a-Elementen, die wiederum innerhalb eines div-Elements liegen. Das gemeinsame „Oberelement“ ist ein div-Container mit der CSS-Klasse „h_img“ bzw. einem weiterne div-Container („h_alle“). Außerdem gibt es einen ausgeblendeten div-Container mit einer Liste von Tablet-Herstellern, diese haben den Begriff „/tablet“ in der href-Angabe und müssen ignoriert werden.

<div id="h_alle">
	<div class="h_img">
		<a href="/hersteller/xyz" title="Handys Hersteller: XYZ">
			<img />
		</a>
	</div>
</div>

Mit „Set allElements1 = HTMLDoc.getElementsByTagName(„a“)“ lasse ich mir also erst alle a-Elemente aus dem Quellcode in meinen „Container“ legen.

Mit der ersten for-each-Schleife durchlaufe ich nun diesen Container und prüfe mit den ersten zwei if-Abfragen, ob sich das a-Element unterhalb der erwähnten div-Container befindet. Da auf der Seite noch ein weitere identische div-Container mit diesen css-Klassen für die Liste der Tablet-Hersteller existiert, muss ich mit einer dritten if-Abfrage die URL des a-Elements überprüfen.
Erst dann kann ich das Attribut des a-Elements auslesen und in mein Excel-Worksheet schreiben.

Fertig ist der erste Schritt – eine Liste der URL zu den jeweiligen Herstellern. Im nächsten Teil werde ich diese Liste durchgehen und von den jeweiligen Seiten die URL zu den Geräten auslesen.

Kategorie: ToolBox, VBA | Tags , , , , , , , , | Kommentieren

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *