Kapitel 1. Dein erster Web Scraper
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Sobald du mit Web Scraping anfängst, wirst du all die kleinen Dinge zu schätzen wissen, die die Browser für dich tun. Das Web ohne HTML-Formatierung, CSS-Styling, JavaScript-Ausführung und Bildwiedergabe kann anfangs etwas einschüchternd wirken, aber in diesem und im nächsten Kapitel werden wir uns damit beschäftigen, wie man Daten ohne die Hilfe eines Browsers formatiert und interpretiert.
Dieses Kapitel beginnt mit den Grundlagen, wie man eine GET
Anfrage (eine Anfrage zum Abrufen des Inhalts einer Webseite) an einen Webserver für eine bestimmte Seite sendet, die HTML-Ausgabe dieser Seite liest und einige einfache Datenextraktionen durchführt, um den gesuchten Inhalt zu isolieren.
Verbinden
Wenn du dich noch nicht viel mit Netzwerken oder Netzwerksicherheit beschäftigt hast, erscheinen dir die Mechanismen des Internets vielleicht ein wenig rätselhaft. Du willst nicht jedes Mal darüber nachdenken, was genau das Netzwerk macht, wenn du einen Browser öffnest und http://google.com aufrufst. Heutzutage musst du das auch nicht mehr. Ich würde sogar behaupten, dass es fantastisch ist, dass die Computerschnittstellen so weit fortgeschritten sind, dass die meisten Menschen, die das Internet nutzen, keine Ahnung haben, wie es funktioniert.
Für das Web Scraping muss jedoch ein Teil dieses Schleiers der Schnittstelle entfernt werden - nicht nur auf der Ebene des Browsers (wie er all dieses HTML, CSS und JavaScript interpretiert), sondern gelegentlich auch auf der Ebene der Netzwerkverbindung.
Um eine Vorstellung von der Infrastruktur zu geben, die erforderlich ist, um Informationen an deinen Browser zu übermitteln, nehmen wir das folgende Beispiel. Alice besitzt einen Webserver. Bob benutzt einen Desktop-Computer, der versucht, sich mit dem Server von Alice zu verbinden. Wenn ein Computer mit einem anderen Computer kommunizieren will, findet etwa der folgende Austausch statt:
-
Bobs Computer sendet einen Strom von 1 und 0 Bits, die durch hohe und niedrige Spannungen auf einer Leitung angezeigt werden. Diese Bits bilden eine Information, die aus einem Header und einem Body besteht. Der Header enthält als unmittelbares Ziel die MAC-Adresse seines lokalen Routers und als endgültiges Ziel die IP-Adresse von Alice. Der Body enthält seine Anfrage an die Serveranwendung von Alice.
-
Bobs lokaler Router empfängt all diese 1en und 0en und interpretiert sie als ein Paket von Bobs eigener MAC-Adresse, das für die IP-Adresse von Alice bestimmt ist. Sein Router versieht das Paket mit seiner eigenen IP-Adresse als Absender-IP-Adresse und schickt es über das Internet.
-
Bobs Paket durchläuft mehrere Zwischenserver, die sein Paket auf den richtigen physikalischen/verkabelten Weg zu Alices Server leiten.
-
Der Server von Alice empfängt das Paket an ihrer IP-Adresse.
-
Der Server von Alice liest den Zielport des Pakets im Header und leitet ihn an die entsprechende Anwendung weiter - die Webserver-Anwendung. (Das Ziel des Paketports ist bei Webanwendungen fast immer Port 80; dies kann man sich wie eine Wohnungsnummer für Datenpakete vorstellen, während die IP-Adresse wie eine Straßenadresse ist).
-
Die Webserver-Anwendung erhält einen Datenstrom vom Server-Prozessor. Diese Daten lauten in etwa wie folgt:
- Dies ist eine
GET
Anfrage.- Die folgende Datei wird angefordert: index.html.
-
Der Webserver findet die richtige HTML-Datei, bündelt sie in einem neuen Paket, das er an Bob sendet, und schickt es an seinen lokalen Router, um es auf dem gleichen Weg zurück zu Bobs Rechner zu transportieren.
Und voilà! Wir haben das Internet.
Also, wo in diesem Austausch kam der Webbrowser ins Spiel? Absolut nirgends. Tatsächlich sind Browser eine relativ neue Erfindung in der Geschichte des Internets, wenn man bedenkt, dass Nexus erst 1990 veröffentlicht wurde.
Ja, der Webbrowser ist eine nützliche Anwendung, die diese Informationspakete erstellt, dein Betriebssystem anweist, sie zu senden, und die Daten, die du zurückbekommst, als hübsche Bilder, Töne, Videos und Texte interpretiert. Ein Webbrowser ist jedoch nur Code, und Code kann in seine Grundbestandteile zerlegt, umgeschrieben und wiederverwendet werden und alles tun, was du willst. Ein Webbrowser kann dem Prozessor mitteilen, dass er Daten an die Anwendung senden soll, die deine drahtlose (oder drahtgebundene) Schnittstelle verwaltet, aber du kannst dasselbe in Python mit nur drei Codezeilen tun:
from
urllib.request
import
urlopen
html
=
urlopen
(
'http://pythonscraping.com/pages/page1.html'
)
(
html
.
read
())
Um dies auszuführen, kannst du das iPython-Notizbuch für Kapitel 1 im GitHub-Repository verwenden oder du kannst es lokal als scrapetest.py speichern und es mit diesem Befehl in deinem Terminal ausführen:
$
python scrapetest.py
Beachte, dass du, wenn du auch Python 2.x auf deinem Rechner installiert hast und beide Python-Versionen nebeneinander laufen, eventuell Python 3.x explizit aufrufen musst, indem du den Befehl auf diese Weise ausführst:
$
python3 scrapetest.py
Dieser Befehl gibt den vollständigen HTML-Code für Seite1 unter der URL http://pythonscraping.com/pages/page1.html aus . Genauer gesagt, wird die HTML-Datei page1.html ausgegeben, die sich im Verzeichnis <web root>/pages auf dem Server mit dem Domainnamen http://pythonscraping.com befindet .
Warum ist es wichtig, diese Adressen als "Dateien" und nicht als "Seiten" zu betrachten? Die meisten modernen Webseiten haben viele Ressourcendateien, die mit ihnen verbunden sind. Dabei kann es sich um Bilddateien, JavaScript-Dateien, CSS-Dateien oder andere Inhalte handeln, mit denen die von dir angeforderte Seite verknüpft ist. Wenn ein Webbrowser auf ein Tag wie <img src="cuteKitten.jpg">
stößt, weiß er, dass er eine weitere Anfrage an den Server stellen muss, um die Daten in der Datei cuteKitten.jpg zu erhalten, damit die Seite für den Nutzer vollständig dargestellt werden kann.
Natürlich hat dein Python-Skript (noch) nicht die Logik, um mehrere Dateien anzufordern; es kann nur die einzelne HTML-Datei lesen, die du direkt angefordert hast.
from
urllib.request
import
urlopen
bedeutet das, wonach es aussieht: Es schaut sich die Python-Modulanforderung an (zu finden in der urllib-Bibliothek ) und importiert nur die Funktion urlopen
.
urllib ist eine standardmäßige Python-Bibliothek (d.h. du musst nichts extra installieren, um dieses Beispiel auszuführen) und enthält Funktionen zum Anfordern von Daten im Web, zum Umgang mit Cookies und sogar zum Ändern von Metadaten wie Kopfzeilen und deinem User Agent. Wir werden urllib im Laufe des Buches ausgiebig nutzen, daher empfehle ich dir, die Python-Dokumentation für die Bibliothek zu lesen.
urlopen
wird verwendet, um ein entferntes Objekt über ein Netzwerk zu öffnen und zu lesen. Da es sich um eine recht allgemeine Funktion handelt (sie kann problemlos HTML-Dateien, Bilddateien oder jeden anderen Dateistrom lesen), werden wir sie im Laufe des Buches recht häufig verwenden.
Eine Einführung in BeautifulSoup
Wunderschöne Suppe, so reichhaltig und grün,
die in einer heißen Terrine wartet!
Wer würde sich für solche Leckereien nicht bücken?
Die Suppe des Abends, die schöne Suppe!
Die BeautifulSoup-Bibliothek wurde nach dem gleichnamigen Gedicht von Lewis Carroll aus Alices Abenteuer im Wunderland benannt. In der Geschichte wird dieses Gedicht von einer Figur namens "Mock Turtle" gesungen (ein Wortspiel mit dem beliebten viktorianischen Gericht "Mock Turtle Soup", das nicht aus Schildkröten, sondern aus Kühen besteht).
Wie, sein Namensvetter aus dem Wunderland, versucht BeautifulSoup, dem Unsinn einen Sinn zu geben; es hilft, das chaotische Web zu formatieren und zu organisieren, indem es schlechtes HTML korrigiert und uns leicht durchschaubare Python-Objekte präsentiert, die XML-Strukturen darstellen.
Installation von BeautifulSoup
Da die BeautifulSoup-Bibliothek keine Standard-Python-Bibliothek ist, muss sie installiert werden. Wenn du bereits Erfahrung mit der Installation von Python-Bibliotheken hast, verwende bitte dein bevorzugtes Installationsprogramm und fahre mit dem nächsten Abschnitt "BeautifulSoup ausführen" fort .
Für diejenigen, die die Python-Bibliotheken noch nicht installiert haben (oder eine Auffrischung benötigen), wird diese allgemeine Methode für die Installation mehrerer Bibliotheken im gesamten Buch verwendet, so dass du diesen Abschnitt in Zukunft nachschlagen solltest.
Wir werden in diesem Buch die BeautifulSoup 4-Bibliothek (auch bekannt als BS4) verwenden. Die vollständige Anleitung für die Installation von BeautifulSoup 4 findest du auf Crummy.com; die grundlegende Methode für Linux wird hier gezeigt:
$
sudo apt-get install python-bs4
Und für Macs:
$
sudo easy_install pip
Unter wird der Python-Paketmanager pip installiert. Führe dann das Folgende aus, um die Bibliothek zu installieren:
$
pip install beautifulsoup4
Auch hier gilt: Wenn du sowohl Python 2.x als auch 3.x auf deinem Rechner installiert hast, musst du eventuell python3
explizit aufrufen:
$
python3 myScript.py
Achte darauf, dies auch bei der Installation von Paketen zu verwenden, da die Pakete sonst möglicherweise unter Python 2.x, aber nicht unter Python 3.x installiert werden:
$
sudo python3 setup.py install
Wenn du pip benutzt, kannst du auch pip3
aufrufen, um die Python 3.x Versionen der Pakete zu installieren:
$
pip3 install beautifulsoup4
Das Installieren der Pakete unter Windows ist fast identisch mit dem Verfahren für Mac und Linux. Lade die neueste Version von BeautifulSoup 4 von der Download-Seite herunter, navigiere zu dem Verzeichnis, in das du es entpackt hast, und führe die folgende Datei aus:
>
python
setup
.
py
install
Und das war's! BeautifulSoup wird jetzt als Python-Bibliothek auf deinem Rechner erkannt. Du kannst das ausprobieren, indem du ein Python-Terminal öffnest und die Bibliothek importierst:
$
python
>
from
bs4
import
BeautifulSoup
Der Import sollte ohne Fehler abgeschlossen werden.
Außerdem gibt es einen . exe-Installer für pip unter Windows, mit dem du ganz einfach Pakete installieren und verwalten kannst:
>
pip
install
beautifulsoup4
Laufen BeautifulSoup
Das am häufigsten verwendete Objekt in der BeautifulSoup-Bibliothek ist passenderweise das BeautifulSoup
Objekt. Schauen wir uns das Objekt in Aktion an, indem wir das Beispiel am Anfang dieses Kapitels abändern:
from
urllib.request
import
urlopen
from
bs4
import
BeautifulSoup
html
=
urlopen
(
'http://www.pythonscraping.com/pages/page1.html'
)
bs
=
BeautifulSoup
(
html
.
read
(),
'html.parser'
)
(
bs
.
h1
)
Die Ausgabe sieht folgendermaßen aus:
<h1>
An Interesting Title</h1>
Beachte, dass dies nur die erste Instanz des h1
Tags auf der Seite zurückgibt. Laut Konvention sollte nur ein h1
Tag auf einer Seite verwendet werden, aber Konventionen werden im Web oft gebrochen, daher solltest du dir bewusst sein, dass dies nur die erste Instanz des Tags abruft und nicht unbedingt die, nach der du suchst.
Wie in früheren Web-Scraping-Beispielen importierst du die Funktion urlopen
und rufst html.read()
auf, um den HTML-Inhalt der Seite zu erhalten. Zusätzlich zum Textstring kann BeautifulSoup auch das Datei-Objekt verwenden, das direkt von urlopen
zurückgegeben wird, ohne dass du zuerst .read()
aufrufen musst:
bs = BeautifulSoup(html, 'html.parser')
Dieser HTML-Inhalt wird dann in ein BeautifulSoup
Objekt mit der folgenden Struktur umgewandelt:
-
html → <html><head>...</head><body>...</body></html>
-
head → <head><title>Eine nützliche Seite<title></head>
- title → <title>Eine nützlicheSeite</title>
-
body → <body><h1>Ein Int...</h1><div>Lorem ip...</div></body>
- h1 → <h1>Ein interessanter Titel</h1>
- div → <div>Lorem Ipsum dolor...</div>
-
Beachte, dass das h1
Tag, das du aus der Seite extrahierst, zwei Schichten tief in deiner BeautifulSoup
Objektstruktur verschachtelt ist (html
→ body
→ h1
). Wenn du es jedoch tatsächlich aus dem Objekt holst, rufst du das h1
Tag direkt auf:
bs
.
h1
Tatsächlich würde jeder der folgenden Funktionsaufrufe die gleiche Ausgabe erzeugen:
bs
.
html
.
body
.
h1
bs
.
body
.
h1
bs
.
html
.
h1
Wenn du ein BeautifulSoup
Objekt erstellst, werden zwei Argumente übergeben:
bs
=
BeautifulSoup
(
html
.
read
(),
'html.parser'
)
Das erste ist der HTML-Text, auf dem das Objekt basiert, und das zweite gibt den Parser an, den BeautifulSoup verwenden soll, um das Objekt zu erstellen. In den meisten Fällen macht es keinen Unterschied, welchen Parser du wählst.
html.parser
ist ein Parser, der in Python 3 enthalten ist und für dessen Verwendung keine zusätzlichen Installationen erforderlich sind. Außer bei Bedarf werden wir diesen Parser im gesamten Buch verwenden.
Ein weiterer beliebter Parser ist lxml. Dieser kann über pip installiert werden:
$
pip3
install
lxml
lxml kann mit BeautifulSoup verwendet werden, indem der mitgelieferte Parser-String geändert wird:
bs
=
BeautifulSoup
(
html
.
read
(),
'lxml'
)
lxml hat einige Vorteile gegenüber dem html.parser, da er im Allgemeinen besser in der Lage ist, "unsauberen" oder schlecht geformten HTML-Code zu analysieren. Er ist nachsichtig und behebt Probleme wie nicht geschlossene Tags, unsachgemäß verschachtelte Tags und fehlende Head- oder Body-Tags. Es ist auch etwas schneller als html.parser
, obwohl Geschwindigkeit beim Web Scraping nicht unbedingt ein Vorteil ist, da die Geschwindigkeit des Netzwerks selbst fast immer der größte Engpass ist.
Einer der Nachteile von lxml ist, dass es separat installiert werden muss und auf C-Bibliotheken von Drittanbietern angewiesen ist, um zu funktionieren. Das kann im Vergleich zum html.parser zu Problemen bei der Portabilität und Benutzerfreundlichkeit führen.
Ein weiterer beliebter HTML-Parser ist html5lib
. Wie lxml ist auch html5lib
ein extrem nachsichtiger Parser, der sogar noch mehr Initiative zeigt, um fehlerhaftes HTML zu korrigieren. Auch er hängt von einer externen Abhängigkeit ab und ist langsamer als lxml und html.parser. Trotzdem kann er eine gute Wahl sein, wenn du mit unordentlichen oder handgeschriebenen HTML-Seiten arbeitest.
Sie kann verwendet werden, indem du den String html5lib
installierst und an das BeautifulSoup-Objekt übergibst:
bs = BeautifulSoup(html.read(), 'html5lib')
Ich hoffe, dieser kleine Vorgeschmack auf BeautifulSoup hat dir einen Eindruck von der Leistungsfähigkeit und Einfachheit dieser Bibliothek gegeben. Praktisch jede Information kann aus jeder HTML- (oder XML-) Datei extrahiert werden, solange sie von einem identifizierenden Tag umgeben oder in der Nähe davon ist. Kapitel 2 geht tiefer in die komplexeren BeautifulSoup-Funktionsaufrufe ein und stellt reguläre Ausdrücke vor und wie sie mit BeautifulSoup
verwendet werden können, um Informationen aus Webseiten zu extrahieren.
Zuverlässig verbinden und Ausnahmen behandeln
Das Web ist chaotisch. Daten sind schlecht formatiert, Websites gehen kaputt und schließende Tags fehlen. Eine der frustrierendsten Erfahrungen beim Web Scraping ist es, mit einem laufenden Scraper schlafen zu gehen und von all den Daten zu träumen, die du am nächsten Tag in deiner Datenbank haben wirst - nur um dann festzustellen, dass der Scraper bei einem unerwarteten Datenformat auf einen Fehler gestoßen ist und die Ausführung beendet hat, kurz nachdem du aufgehört hast, auf den Bildschirm zu schauen. In solchen Situationen bist du vielleicht versucht, den Namen des Entwicklers zu verfluchen, der die Website (und die seltsam formatierten Daten) erstellt hat, aber die Person, die du eigentlich treten solltest, bist du selbst, weil du die Ausnahme nicht von vornherein vorausgesehen hast!
Schauen wir uns die erste Zeile unseres Scrapers nach den Import-Anweisungen an und überlegen wir uns, wie wir mit den Ausnahmen umgehen, die dabei auftreten können:
html
=
urlopen
(
'http://www.pythonscraping.com/pages/page1.html'
)
In dieser Branche können vor allem zwei Dinge schiefgehen:
- Die Seite wurde auf dem Server nicht gefunden (oder es gab einen Fehler beim Abrufen der Seite).
- Der Server wird nicht gefunden.
In wird im ersten Fall ein HTTP-Fehler zurückgegeben. Dieser HTTP-Fehler kann "404 Page Not Found", "500 Internal Server Error" und so weiter lauten. In all diesen Fällen wird die Funktion urlopen
die allgemeine Ausnahme HTTPError
auslösen. Du kannst diese Ausnahme auf folgende Weise behandeln:
from
urllib.request
import
urlopen
from
urllib.error
import
HTTPError
try
:
html
=
urlopen
(
'http://www.pythonscraping.com/pages/page1.html'
)
except
HTTPError
as
e
:
(
e
)
# return null, break, or do some other "Plan B"
else
:
# program continues. Note: If you return or break in the
# exception catch, you do not need to use the "else" statement
Wenn ein HTTP-Fehlercode zurückgegeben wird, druckt das Programm nun den Fehler aus und führt den Rest des Programms unter der Anweisung else
nicht aus.
Wenn der Server überhaupt nicht gefunden wird (z.B. weil http://www.pythonscraping.com nicht erreichbar ist oder die URL falsch eingegeben wurde), wirft urlopen
einen URLError
aus. Das bedeutet, dass kein Server erreicht werden konnte, und da der entfernte Server für die Rückgabe von HTTP-Statuscodes verantwortlich ist, kann ein HTTPError
nicht ausgelöst werden, und der schwerwiegendere URLError
muss abgefangen werden. Du kannst eine Prüfung hinzufügen, um zu sehen, ob dies der Fall ist:
from
urllib.request
import
urlopen
from
urllib.error
import
HTTPError
from
urllib.error
import
URLError
try
:
html
=
urlopen
(
'https://pythonscrapingthisurldoesnotexist.com'
)
except
HTTPError
as
e
:
(
e
)
except
URLError
as
e
:
(
'The server could not be found!'
)
else
:
(
'It Worked!'
)
Wenn die Seite erfolgreich vom Server abgerufen wurde, besteht natürlich immer noch das Problem, dass der Inhalt der Seite nicht ganz den Erwartungen entspricht. Jedes Mal, wenn du auf ein Tag in einem BeautifulSoup
Objekt zugreifst, ist es klug, eine Prüfung einzufügen, um sicherzustellen, dass das Tag tatsächlich existiert. Wenn du versuchst, auf ein Tag zuzugreifen, das nicht existiert, gibt BeautifulSoup ein None
Objekt zurück. Das Problem ist, dass der Versuch, auf ein Tag in einem None
Objekt zuzugreifen, dazu führt, dass ein AttributeError
ausgelöst wird.
Die folgende Zeile (wobei nonExistentTag
ein erfundener Tag ist, nicht der Name einer echten BeautifulSoup-Funktion)
(
bs
.
nonExistentTag
)
gibt ein None
Objekt zurück. Es ist völlig in Ordnung, mit diesem Objekt umzugehen und es zu überprüfen. Problematisch wird es, wenn du es nicht überprüfst, sondern stattdessen versuchst, eine andere Funktion auf dem None
Objekt aufzurufen, wie im Folgenden dargestellt:
(
bs
.
nonExistentTag
.
someTag
)
Dies gibt eine Ausnahme zurück:
AttributeError
:
'NoneType'
object
has
no
attribute
'someTag'
Wie kannst du dich also vor diesen beiden Situationen schützen? Am einfachsten ist es, wenn du explizit auf beide Situationen achtest:
try
:
badContent
=
bs
.
nonExistingTag
.
anotherTag
except
AttributeError
as
e
:
(
'Tag was not found'
)
else
:
if
badContent
==
None
:
(
'Tag was not found'
)
else
:
(
badContent
)
Diese Überprüfung und Behandlung jedes Fehlers scheint zunächst mühsam zu sein, aber es ist einfach, diesen Code ein wenig umzugestalten, damit er weniger schwierig zu schreiben (und, was noch wichtiger ist, viel weniger schwierig zu lesen) ist. Dieser Code ist zum Beispiel unser Scraper, der etwas anders geschrieben wurde:
from
urllib.request
import
urlopen
from
urllib.error
import
HTTPError
from
bs4
import
BeautifulSoup
def
getTitle
(
url
):
try
:
html
=
urlopen
(
url
)
except
HTTPError
as
e
:
return
None
try
:
bs
=
BeautifulSoup
(
html
.
read
(),
'html.parser'
)
title
=
bs
.
body
.
h1
except
AttributeError
as
e
:
return
None
return
title
title
=
getTitle
(
'http://www.pythonscraping.com/pages/page1.html'
)
if
title
==
None
:
(
'Title could not be found'
)
else
:
(
title
)
In diesem Beispiel erstellst du eine Funktion getTitle
, die entweder den Titel der Seite oder ein None
Objekt zurückgibt, wenn es ein Problem beim Abrufen der Seite gab. Innerhalb von getTitle
überprüfst du wie im vorherigen Beispiel, ob ein HTTPError
vorhanden ist, und kapselst zwei BeautifulSoup-Zeilen in einer try
Anweisung. Ein AttributeError
könnte von jeder dieser Zeilen ausgelöst werden (wenn der Server nicht existiert, wäre html
ein None
Objekt und html.read()
würde ein AttributeError
auslösen). Du könntest so viele Zeilen wie du willst in eine try
Anweisung einfügen oder eine ganz andere Funktion aufrufen, die an jedem beliebigen Punkt eine AttributeError
auslösen kann.
Wenn du Scraper schreibst, ist es wichtig, dass du über das Gesamtmuster deines Codes nachdenkst, um Ausnahmen zu behandeln und ihn gleichzeitig lesbar zu machen. Du wirst wahrscheinlich auch viel Code wiederverwenden wollen. Mit generischen Funktionen wie getSiteHTML
und getTitle
(mit gründlicher Ausnahmebehandlung) machen es einfach, das Web schnell und zuverlässig zu scrapen.
Get Web Scraping mit Python, 2. Auflage now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.