Daten über HTTP herunterladen und Web-Formulare ausfüllen mit PowerShell

    Datei-Download mit PowerShell Invoke-WebRequestMicrosoft erweiterte PowerShell 3.0 um Funk­tionen, die ähnliche Aufgaben bewältigen können wie cURL oder wget. Dazu zählt in den ein­fach­sten Fällen der Download von Dateien über HTTP(S) bzw. FTP. Da aber auch alle Mittel für das Parsing von HTML-Seiten zur Verfügung stehen, könnte man Scripts auch für das Monitoring von Websites einsetzen.

    Obwohl es mehrere Cmdlets gibt, mit denen man eine Kommunikation über HTTP anstoßen oder Inhalte parsen kann, spielt Invoke-WebRequest dabei die wesentliche Rolle. Es kann nicht nur Dateien und Web-Seiten herunterladen, sondern es bietet auch die nötigen Methoden, um durch den DOM-Baum eines HTML-Dokuments zu navigieren oder um Formulare zu versenden.

    Download von Dateien als einfachste Anwendung

    Das Herunterladen von Dateien von der Kommandozeile oder in Scripts ist etwa dann praktisch, wenn man keinen funktionierenden Browser zur Verfügung hat. Das ist beispielsweise der Fall, wenn man auf einem Windows Server arbeitet und nicht die verstärkte Sicherheitskonfiguration des IE deaktivieren möchte.

    Die Nutzung von PowerShell für den Download einer Datei ist sehr einfach:

    Invoke-WebRequest "http://download.pdfforge.org/download/pdfcreator/PDFCreator-stable?download" -Outfile PDFCreator.exe

    Dieses Beispiel lädt den kostenlosen PDFCreator herunter und speichert ihn im aktuellen Verzeichnis als PDFCreator.exe. Auf die gleiche Weise könnte man eine Datei von einem FTP-Server herunterladen, wenn man die URI entsprechend anpasst und http:// durch ftp:// ersetzt.

    Return-Code und HTTP-Header prüfen

    Lädt man eine binäre Datei herunter oder öffnet man eine Web-Seite, dann gibt Invoke-WebRequest Auskunft über die erfolgte HTTP-Kommunikation. Diese Informationen liegen als Eigenschaften des zurückgegebenen HtmlWebResponseObject vor.

    Aufschlussreich für die Analyse der HTTP-Übertragung sind etwa der Statuscode sowie der HTTP-Header:

    $wp = Invoke-WebRequest "http://de.wikipedia.org/"

    Nach diesem Aufruf der Wikipedia-Homepage könnte man über $wp.StatusCode erfahren, ob Fehler aufgetreten sind. Der verwendete Zeichensatz und der Content-Type ließe sich über

    $wp.Headers["Content-Type"]

    ermitteln.

    Parsing von HTML-Seiten

    Bei HTML-Seiten liegen wesentliche Elemente direkt als Eigenschaften des zurückgegebenen Objekts vor. Dazu zählen alle auf der Web-Page enthaltenen Links (links), Formulare (forms), Bilder (images) oder Scripts (scripts).

    Wenn man zum Beispiel den Wikipedia-Eintrag zu Chiemsee abruft:

    $img = Invoke-WebRequest "http://de.wikipedia.org/wiki/Chiemsee"

    dann erhält man eine Sammlung aller in der Seite enthaltenen Bilder mit

    $img.Images

    Möchte man die Ausgabe noch filtern, etwa indem man nur jene Bilder wählt, die zwischen 100 und 199 Pixel breit sind, dann hilft ein Vergleich unter Verwendung eines regulären Ausdrucks:

    $img.Images | where {$_.width -match "1.{2,}"}

    Web-Seiten überwachen

    Eine interessante Anwendung für Invoke-WebRequest könnte in einem Screen Scraping bestehen, bei dem man kritische Informationen aus einer Seite ausliest, um etwa einen fehlerhaften Zustand der Website zu ermitteln. Für diese Aufgabe gibt es mehrere Möglichkeiten, ein bestimmtes Element auszuwählen.

    Eine davon ist die Methode FindById, mit der man direkt auf einen Knoten zugreifen kann, der über ein id-Attribut eindeutig bezeichnet wird. Der folgende Befehl würde beispielsweise zum Element <div id="Weblinks">Die Liste meiner Links</div> führen:

    $img.AllElements.FindById("Weblinks")

    Deutet innerhalb eines Abschnitts eine bestimmte Zeichenkette darauf hin, dass Probleme vorliegen, dann kann man diese relativ einfach finden:

    $img.AllElements.FindById("Comments") | where innerhtml -like *Viagr*

    Wenn in diesem Fall eine Sektion mit der ID Comments die Zeichenkette "Viagr" enthält, so wäre das wahrscheinlich ein Hinweis auf Spam. In einem Script könnte man dann eine Benachrichtigung per Mail versenden, damit der Webmaster die Site bereinigt.

    Filter anhand von Tag-Namen und Attributen

    Ein alternatives Vorgehen, um bestimmte HTML-Fragmente zu extrahieren, besteht im Filtern mit Hilfe von Elementnamen und Attributwerten:

    $img.AllElements | where {$_.tagName -eq "p" -and $_.class -eq "content"}

    In diesem Aufruf erhält man alle Absätze (also <p>-Elemente), die mit der CSS-Klasse content formatiert wurden.

    Ein weiteres Beispiel zeigt, wie man bei einer Google-Suche nach Windows Server 2012 R2 die reinen Textinformationen aus den SERPs ausliest.

    $goog = Invoke-WebRequest "http://www.google.de/search?q=Windows+Server+2012+R2"
    $goog.AllElements|where {$_.class -eq "g"} | select innertext|fl

    Der zweite Befehl filtert alle Elemente aus, deren class-Attribut den Wert "g" hat (und das sind bei Google die Listenelemente für die einzelnen Treffer) und extrahiert über die Eigenschaft innertext den Textanteil des Knotens. innerhtml dagegen liefert den gesamten Inhalt des Elements inklusive Markup, und outerhtml umfasst zusätzlich das Element selbst.

    Formulare ausfüllen und abschicken

    Viele Web-Dienste erfordern eine Anmeldung, wobei diese in der Regel über ein HTML-Formular erfolgt. Mit Hilfe von Invoke-WebRequest kann man eine Anmeldeseite herunterladen, die benötigten Felder ausfüllen und danach das Formular abschicken.

    Wichtig ist dabei jedoch für den Abruf von Folgeseiten, dass der HTTP-Agent Cookies speichern kann, weil sonst die Session endet. Solche Verbindungsinformationen wie Anmeldedaten, Cookies oder die Bezeichnung des User Agent speichert das PowerShell-Cmdlet in einer Session-Variablen. Sie sollte man bereits beim Aufruf der Login-Seite verwenden:

    $dig = Invoke-WebRequest http://www.diigo.com/sign-in -SessionVariable session

    Dieses Beispiel führt anhand des Bookmark-Dienstes Diigo vor, wie man sich über ein Formular authentifizieren kann. Zu beachten ist hier, dass der Parameter SessionVariable den Namen der Variablen ohne führendes Dollarzeichen erwartet.

    Im nächsten Schritt muss man herausfinden, welches Formular zuständig ist und wie seine Felder heißen. Nach der Eingabe von $dig.forms stellt sich heraus, dass der Name des Formulars loginForm lautet. Seine Felder ermittelt man mit

    $dig.Forms["loginForm"].Fields

    Nun kann man diese mit den gewünschten Werten belegen:

    $dig.Forms["loginForm"].Fields["username"] = "Meine@Mail-Adresse.de"
    $dig.Forms["loginForm"].Fields["password"] = "P@sswort"
    $dig.Forms["loginForm"].Fields["referInfo"] = ""

    Um das Formular abzuschicken, muss man jene Anwendung aufrufen, die im action-Attribut des Formulars angegeben ist. Diese kann man entweder selbst aus der Quelltext auslesen oder man verwendet die Action-Eigenschaft des Formulars. Wenn dort nur ein relativer Pfad angegeben ist, muss man die Domäne selbst voranstellen:

    $log = Invoke-WebRequest -Method POST -URI ("https://www.diigo.com" + $dig.Forms["loginForm"].action) -Body $dig.Forms["loginForm"].Fields -WebSession $session

    Zu erwähnen wäre hier, dass die Formulardaten mit dem Parameter Body an den Web-Server übergeben werden. Die zu verwendende HTTP-Methode hängt davon ab, welche die Web-Anwendung verlangt, meistens ist es POST. Schließlich leitet man die Session-Informationen, die man beim ersten Aufruf in einer Variablen gespeichert hat, über den Parameter WebSession weiter, wobei hier beim Variablennamen das führende Dollarzeichen nicht fehlen darf.

    1 Kommentar

    Bild von niff_la
    niff_la sagt:
    16. Februar 2015 - 21:26

    Vielen Dank!
    Der Artikel hat mir echt geholfen. :)