Beautiful Soup: HTML Website parsen, Daten auslesen | Python Beispiel „Benzinpreise“

Nach all den theoretischen Ratschlägen rund um Python geht es jetzt an ein praktisches Beispiel. Konkret soll eine Website gescrapt werden, die aktuelle Kraftstoffpreise aus Polen veröffentlicht und für die es keine (kostenlose) API gibt.

So einfach wie in den vielen Tutorials ist es aber selten. In all den Beispielen und Anleitungen liegen die Daten schön separiert in einem „div“ vor, der eine eindeutige „class“ mit sich bringt. Bei solchen idealen „Problemstellungen“ lässt sich jeder gern davon überzeugen, das Python DIE ideale Sprache für Anfänger ist – selbst wenn es um das Auslesen von Informationen aus Homepages geht. Die Praxis stellt Dich jedoch vor komplizierten Aufgaben, die nicht mit 3 bis 8 Zeilen Python-Code zu realisieren sind. Zumindest nicht für einen Anfänger, Einsteiger.

Webmaster ändern manchmal den Quellcode ihrer Webseiten, weshalb ein Scraper irgendwann möglicherweise nicht mehr funktioniert. Eine Möglichkeit, diese Änderungen zu überwachen, besteht darin, eine Continous Integration einzurichten. Dadurch wird dein Scraper-Skript regelmäßig getestet und sobald Fehler auftreten, wirst du informiert.

Nutze zur Analyse der Website am besten die Entwicklertools deines Browsers. Beim Firefox nennen sie sich“Firefox Tools für Webentwickler“ und sind über die Funktionstaste F12 erreichbar. Im Chrome übrigens auch.

Die Aufgabe

Im vorliegenden Beispiel sollen aus DIESER Website die aktuellen Diesel- und Benzinpreise ausgelesen werden. Im Gegensatz zu „Schulungs-Beispielen“ ist Pustekuchen mit säuberlich ausgezeichneten Strukturen. Die relevanten Daten befinden sich in einer unscheinbaren Tabelle, die in einem von mehreren „divs“ mit derselben „class“ liegen.

<div class="box">
    <h3 class="page-header">Treibstoffpreise heute</h3>
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Treibstoff</th>
                <th>Durchschnittlich in Słubice</th>
                <th>Durchschnittlich in Polen</th>
            </tr>
        </thead>
        <tr>
            <td><img src="/img/fuels/default/gasoline.png" alt="" /> Benzin 95</td>
            <td>6,76 PLN/L</td>
            <td>6,63 PLN/L</td>
        </tr>
        <tr>
            <td><img src="/img/fuels/default/diesel.png" alt="" /> Diesel</td>
            <td>7,58 PLN/L</td>
            <td>7,42 PLN/L</td>
        </tr>
       <tr>
            <td><img src="/img/fuels/default/lpg.png" alt="" /> Autogas</td>
            <td>3,72 PLN/L</td>
            <td>3,72 PLN/L</td>
        </tr>
        <tr>
            <td><img src="/img/fuels/default/gasoline98.png" alt="" /> Benzin 98</td>
            <td>7,05 PLN/L</td>
            <td>7,05 PLN/L</td>
        </tr>
	</table>
</div>

Wie bringen wir Python bei, diesen HTML-Code zu verstehen? Wir müssen zunächst in den HTML-Tags ein Schema erkennen, mit dessen Hilfe die Programmierer die Daten einkapseln, uns präsentieren.

Der Codeblock mit den Daten wird von einem DIV umschlossen:

<div class="box">
	...
	...
</di>

Die Inhalte in diesem DIV möchten wir auswerten. Wir schreiben uns jetzt folgendes kleines Script.

#!/usr/bin/python3	
# ===== Abfangen von Fehlern
try:
	# ===== Damit Ausgabe im Browser erfolgen kann
	print ("Content-Type: text/html; charset=UTF-8\n")

	# ===== Module importieren
	#       Ggf. mit Befehl "pip3 install MODULNAME"  auf Server per SSH installieren
	import sys
	import requests
	from bs4 import BeautifulSoup

	# ===== URL festlegen, die eingelesen werden soll 
	baseURL = 'https://de.fuelo.net'
	stadt= '148291'
	URL = baseURL + '/gasstations/settlement/' + stadt + '?lang=de'
	
	# ===== HTTP Header
	head = {'User-Agent': 'Mozilla/4.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTM, like Gecko) Chrome/51.0.2704.103 Safari/537.36 '}
	
	# ===== Webseite aufrufen und parsen
	webseite   = requests.get(URL, headers = head)
	quelltext  = BeautifulSoup(webseite.text, 'html.parser')

	# ===== Inhalte auslesen
	inhalt_div = quelltext('div', {'class':'box'})
	table_tr   = inhalt_div[0]('tr')
	
	print ( f"Anzahl Zeilen: { len(inhalt_table_tr)} | Zeile 1 ist Kopfzeile ")
	print(inhalt_div[0])
	print(table_tr  [0])


except Exception as e:
    print("Fehlermeldung:", e)

In diesem Beispiel ist das Problem folgendes – die Daten werden nicht explizit mit „class“ oder „id“ gekennzeichnet. Die aktuellen Dieselpreise für die Stadt Slubize stehen stattdessen einsam im 2. <td>-Tag des 3. <tr>-Tag.

Als PHP-Programmierer ist es mir geläufig, wie ich aus dem Quellcode bestimmte Abschnitte mit Delmitern und regulären Ausdrücken für eine weitere Analyse „herausschneide“. Da hat man Übung und ein PHP-Scraper ist innerhalb weniger Minuten geschrieben. Wie läuft das aber nun mit „Python für Anfänger“?

Die Bibliothek Beautiful Soup

Diese Bibliothek wurde geschaffen, um Informationen aus Webseiten „herauszuschneiden“. Doch aufgepasst, es gibt mehrere Versionen dieser Bibliothek, die nicht mit jeder Python Version funktioniert.

Die aktuelle und neuere Bibliothek Beautiful Soup 4 arbeitet mit Python 3 und Pythons 2. Beautiful Soup 3 hingegen funktioniert nur mit Python 2.

Tutorial zum Scraping mit dem Modul requests und Beautiful Soup 4.

Die Bibliothek „pandas“

Pandas ist eine Python-Bibliothek für die Analyse und Verarbeitung von Tabellen-Inhalten. Sie ist dann von Vorteil, wenn Tabellendaten

Aber lesen wir zunächst die Webseite ein.

import requests					 # Modul 'requests' zum Aufrufen von Webseiten wird importieet
from bs4 import BeautifulSoup	 # Aus der Bibliothek 'Bs4' wird das Modul 'BeautifulSoup'geladen

baseURL = 'https://de.fuelo.net' 								# Basis-URL
stadt= '148291' 												# Parameter für die Stadt 'Slubize'
URL = baseURL + '/gasstations/settlement/' + stadt + '?lang=de' # komplette url
header = {'User-Agent': 'Mozilla/4.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTM, like Gecko) Chrome/51.0.2704.103 Safari/537.36 '}
# ein Header kann nützlich sein, falls Websiten unbekannte Scraper blockeiren

InhaltWebseite = requests.get(URL, headers = header) # Inhalt der angefragrten Webseite

soup = BeautifulSoup(InhaltWebseite.text, 'html.parser') # geparster Inhalt 

# DivPreiseAktuell = soup('div', {'class':'box'})