Web Scraping mit Python

In den hier veröffentlichten Blogbeiträgen vergangener Jahre wurde gezeigt, wie Daten, die als CSV- oder JSON-Datei vorliegen, mit Python abgerufen werden können. Doch wie kann auf Elemente einer Webseite, also eine HTML-Datei zugegriffen werden? Die Antwort auf diese Frage ist Gegenstand dieses Artikels. Es geht hier also um das Web Scraping, für das in Python beispielsweise die Bibliothek Beautiful Soup verwendet werden kann. In der Dokumentation heißt es dazu:

Beautiful Soup is a Python library for pulling data out of HTML and XML files.

Mit dieser Bibliothek können also nicht nur HTML- sondern auch XML-Dateien ausgewertet werden. In diesem Beispiel wird es aber nur um HTML gehen. So eine Datei könnte auf der lokalen Festplatte gespeichert sein. Um es aber interessanter zu machen, soll es hier zunächst darum gehen, eine Datei aus dem Internet abzurufen. Für diesen Zweck habe ich die Beispielseite „ws01.html“ bereitgestellt, die folgenden Inhalt hat:

<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8" />
    <title>Meine Internetseite</title>
</head>
<body>

  <header>
    <h1>Willkommen auf meiner Homepage!</h1>
    <h2>Hier findet Ihr viele Infos zur Programmierung</h2>
  <header>

  <article>
    <p>Die <a href="https://dotnet.microsoft.com/en-us/">Entwicklungsplattform .NET</a> 
    ist 20 Jahre alt geworden. Das freut mich und ist zugleich überraschend. 
    Denn es gab Zeiten, in denen es so aussah, das .NET keine Zukunft haben würde. 
    Denn im Jahre 2011 stellte Microsoft die "Windows Runtime API" (WinRT) vor, die nicht auf .NET basierte. 
    Außerdem wurde <a href="https://developer.mozilla.org/en-US/docs/Web/javascript">JavaScript</a> 
    dem hauseigenen C# bevorzugt.
    </p>
  </article>

</body>
</html>

Zum Einsatz für den Datenabruf kommt wieder einmal requests, eine Bibliothek, die ich bereits in anderen Tutorials vorgestellt habe. Um nun den Inhalt der HTML-Seite abzurufen, reicht folgender Code aus:

import sys
import requests

URL = "https://xern.de/examples/ws01.html"


def fetch_data():
    try:
        response = requests.get(URL)
        response.encoding = "utf-8"
    except requests.ConnectionError as e:
        sys.exit(f"Unable to retrieve data:\n{e!r}")

    return response.text


def main():
    website_data = fetch_data()
    print(website_data)


if __name__ == "__main__":
    main()

Dazu zwei kurze Anmerkungen: Zum einen habe ich die von requests bereitgestellte ConnectionError-Ausnahme hinzugefügt. Sie greift im Falle eines etwaigen Verbindungsfehlers. Zum anderen wird mit der Zeile

response.encoding = "utf-8"

sichergestellt, dass tatsächlich UTF-8 als Zeichenkodierung verwendet wird.

Da wir die Daten jetzt abgerufen haben, können sie ausgewertet werden. Mit anderen Worten: Jetzt kommt Beautiful Soup zum Einsatz.

Diese Bibliothek muss selbstverständlich auf dem System installiert sein:

$ pip install beautifulsoup4  # Windows
$ pip3 install beautifulsoup4 # macOS, Linux

Außerdem ist eine weitere Importanweisung hinzuzufügen:

from bs4 import BeautifulSoup

Die HTML-Daten könnten jetzt in einer Funktion parse_data() wie folgt ausgewertet werden:

def parse_data(data):
    soup = BeautifulSoup(data, "html.parser")
    print(soup)

Im Vergleich zur bisherigen Ausgabe mit

print(website_data)

ist kein großer Unterschied zu sehen. Versuchen wir nun also einzelne HTML-Elemente zu lesen. Den Anfang macht der Titel der Seite (title tag):

print(soup.title)

Dies führt zur Ausgabe von

<title>Meine Internetseite</title>

Mit einer kleinen Änderung in

print(soup.title.get_text(strip=True))

wird nur der Seitentitel „Meine Internetseite“ angezeigt.

Als nächstes sollen die Links ermittelt werden. Diese sehen in HTML wie folgt aus:

<a href="">Link</a>

Es muss also der <a>-Tag abgefragt werden. Und dann das Attribut href. Um alle Links zu erhalten, könnte dies mit folgendem Code erreicht werden:

soup = BeautifulSoup(data, "html.parser")
tags = soup("a")
for tag in tags:
    print(tag.get("href"))

Das führt in diesem Fall dazu, dass zwei Links ausgegeben werden:

https://dotnet.microsoft.com/en-us/
https://developer.mozilla.org/en-US/docs/Web/javascript

Das ließe sich übrigens auch anders aufschlüsseln:

for tag in tags:
    print("Tag: ", tag.get("href"))
    print("Inhalt: ", tag.contents[0])
    print("Attribute:", tag.attrs)

Der komplette Code sieht jetzt wie folgt aus:

import sys
import requests

from bs4 import BeautifulSoup


URL = "https://xern.de/examples/ws01.html"


def fetch_data():
    try:
        response = requests.get(URL)
        response.encoding = "utf-8"
    except requests.Timeout as e:
        sys.exit(f"Unable to retrieve data:\n{e!r}")
    except requests.ConnectionError as e:
        sys.exit(f"Unable to retrieve data:\n{e!r}")

    return response.text


def parse_data(data):
    soup = BeautifulSoup(data, "html.parser")
    tags = soup("a")
    for tag in tags
        print("Tag: ", tag.get("href"))
        print("Inhalt: ", tag.contents[0])
        print("Attribute:", tag.attrs)


def main():
    website_data = fetch_data()
    parse_data(website_data)


if __name__ == "__main__":
    main()

Ich denke, dass mit dieser kurzen Einführung das Grundprinzip von BeautifulSoup deutlich geworden ist. Ein weiteres Beispiel findet Ihr bei Github; es heißt fetch-hackernews, und wie der Name erahnen lässt, werden damit die aktuellen Artikel (Hackernews) von der Internetseite news.ycombinator.com abgefragt.

Eine Übersicht zu weiteren Python-Artikeln findet Ihr auf dieser Seite.