Wenn Sie in den USA leben und schon einmal versucht haben, ein Haus zum Kauf oder zur Miete zu suchen, sind Sie sicher schon auf Zillow gestoßen. Zillow ist eine der größten Immobilien-Websites der Welt und bedient jeden Monat die Bedürfnisse von Millionen von Nutzern. Mit einer solch riesigen Nutzerbasis und Hunderttausenden von Immobilienangeboten ist Zillow eine wahre Fundgrube an Daten.
In diesem Artikel erfahren Sie, wie Sie die Suchergebnisdaten von Zillow abrufen und dabei die Bot-Erkennung umgehen können. Sie werden Selenium und Undetected-Chromedriver verwenden, um die ganze schwere Arbeit zu erledigen. Bevor Sie fortfahren, können Sie sich die Suchergebnisseite von Zillow unter diesem Link ansehen. So sieht sie zum Zeitpunkt der Erstellung dieses Artikels aus:
Inhaltsübersicht
Anforderungen
Legen Sie wie bei jedem neuen Projekt einen neuen Ordner an. Sie können ihn benennen, wie Sie wollen. Erstellen Sie dann eine neue app.py-Datei in diesem Ordner und installieren Sie zwei Abhängigkeiten:
Selenium stellt uns APIs zur programmatischen Steuerung eines Browsers zur Verfügung und undetected-chromedriver patcht den Selenium Chromedriver so, dass Websites nicht wissen, dass ein automatisierter Browser auf sie zugreift. Dies ist in der Regel mehr als genug, um die meisten Anti-Bots zu vereiteln. Kombinieren Sie dies mit einer Lösung für rotierende Proxies, ähnlich der von ScraperAPI, und Sie haben eine ziemlich gute Lösung für das Scraping der meisten Websites.
Sie können diese Befehle im Terminal ausführen, um schnell zu booten und alle Anforderungen zu erfüllen:
$ mkdir scrape-zillow
$ cd scrape-zillow
$ touch app.py
$ pip install selenium undetected-chromedriver
Dieses Tutorial verwendet die Python-Version 3.10.0, aber der Code ist sehr allgemein gehalten und sollte mit den meisten aktuellen Python-Versionen funktionieren. Wenn Sie ungeduldig sind, können Sie sich auch den vollständigen Code am Ende des Artikels ansehen.
Abruf der Zillow-Suchseite
Um sicherzugehen, dass alles korrekt eingerichtet ist, fügen Sie diesen Code in die Datei app.py ein und führen ihn aus:
import undetected_chromedriver as uc
driver = uc.Chrome(use_subprocess=True)
driver.get("https://www.zillow.com/bellevue-wa/")
Wenn alles gut geht, sollten Sie von einem Chrome-Fenster begrüßt werden, das zur Zillow-Suchergebnisseite für Bellevue, WA navigiert. Wenn etwas schief geht und der Code eine Fehlermeldung ausgibt, beheben Sie ihn mit Google (oder vielleicht mit ChatGPT!). Sobald Sie den Fehler behoben haben, können Sie zum nächsten Schritt übergehen.
Wichtig ist, dass die Suchanfragen in der URL kodiert sind. Schauen Sie sich die URL im obigen Code an und ersetzen Sie bellevue-wa durch san-francisco-ca. Es werden nun Immobilien aus San Francisco, CA, angezeigt. Das bedeutet, dass Sie die URL einfach ändern können, indem Sie den gewünschten Ort hinzufügen, und sie sollte weiterhin wie erwartet funktionieren. Dies ist wichtig zu wissen, da der Rest des Artikels dies nicht mehr behandeln wird.
Entscheiden, was verschrottet werden soll
Jetzt, da die Basiseinrichtung gut funktioniert, ist es sehr wichtig, dass Sie sich entscheiden, welche Daten Sie scrapen möchten, bevor Sie weitermachen. Die Arbeit an einem Web Scraping-Projekt ohne Plan ist ein Rezept für ein Desaster. Im Idealfall haben Sie das schon herausgefunden, bevor Sie auch nur eine Zeile Code anfassen.
In diesem Tutorial werden Sie die folgenden Informationen über ein Immobilienangebot auslesen:
- Immobilienpreis
- Anzahl der Betten
- Anzahl der Bäder
- Quadratmeterzahl (Fläche)
- Adresse
Der Screenshot unten zeigt, wo all diese Informationen auf der Seite mit den Suchergebnissen aufgeführt sind:
Die endgültige Ausgabe des Codes sieht dann so aus:
[{
'price': '$2,198,600',
'beds': '5',
'baths': '5',
'sqft': '4,030',
'address': '17003 SE 14th Lane, Bellevue, WA 98008'
}, {
'price': '$26,000,000',
'beds': '6',
'baths': '7',
'sqft': '11,104',
'address': '905 Shoreland Drive SE, Bellevue, WA 98004'
}, {
'price': '$1,465,000',
'beds': '3',
'baths': '3',
'sqft': '2,280',
'address': '6358 121st Avenue SE, Bellevue, WA 98006'
}]
Scraping der Auflistungskarten
Die unentbehrlichsten Werkzeuge für jeden Web Scraper sind die in den meisten Browsern enthaltenen Developer Tools, und fast alle Web Scraping-Projekte beinhalten eine Variation desselben grundlegenden Workflows. Sie werden die Entwicklertools verwenden, um den HTML-Code der Seite zu untersuchen und dann einige XPath- oder CSS-Selektor-Abfragen (mit find_element & find_elements) in der Python REPL ausführen, um sicherzustellen, dass Sie die erwarteten Daten erhalten.
Dies ist der beste Zeitpunkt, um den HTML-Code der Webseite zu untersuchen. Klicken Sie mit der rechten Maustaste auf einen Bereich, der Sie interessiert, und klicken Sie auf "Untersuchen". Dies sollte das Elemente-Panel der Entwicklertools öffnen. Schauen Sie sich um, ob Sie die Tags ausfindig machen können, in denen die benötigten Daten enthalten sind.
Es sieht so aus, als ob jede Immobilienkarte in ein Artikel-Tag verpackt ist und jedes dieser Artikel-Tags ein data-test-Attribut hat, das auf property-card eingestellt ist:
Sie können diese Informationen nutzen, um einen Code zu schreiben, mit dem Sie all diese einzelnen Listenkarten in eine Liste extrahieren können. Gehen Sie zu app.py und fügen Sie den folgenden Code hinzu:
from selenium.webdriver.common.by import By
# ...truncated...
properties = driver.find_elements(By.XPATH, "//article[@data-test="property-card"]")
Dieser Code verwendet eine XPath-Abfrage, um die Auflistungskarten zu extrahieren. Sie können die Abfrage wie folgt dekonstruieren und lesen:
//article
: Alle Artikel-Tags in der HTML-Datei finden[@data-test="property-card"]
: Die das Attribut data-test auf property-card gesetzt haben
Scraping einzelner Listing-Daten
Nun, da Sie alle Immobilienangebote in einer Liste haben, können Sie sie in einer Schleife durchgehen und die gewünschten Informationen aus jedem Angebot extrahieren.
Preisdaten extrahieren
Sehen Sie sich den HTML-Code etwas genauer an und versuchen Sie, die eindeutig identifizierbaren Tags herauszufinden, die die Preisinformationen kapseln.
Es sieht so aus, als ob diese Informationen durch eine Spanne mit dem data-test-Attribut property-card-price gekapselt sind:
Fügen wir der Datei app.py noch etwas Code hinzu, um eine Schleife über alle Eigenschaften zu ziehen und ihren Preis zu extrahieren:
# ...
all_properties = []
for property in properties:
property_data = {}
property_data['price'] = property.find_element(By.XPATH, ".//span[@data-test="property-card-price"]").text
all_properties.append(property_data)
Der Code definiert eine neue all_properties-Liste, um die extrahierten Daten für jede Eigenschaft zu speichern. Anschließend werden alle extrahierten Eigenschaftskarten in einer Schleife durchlaufen und die Preisinformationen mithilfe einer XPath-Abfrage extrahiert. Der einzige wesentliche Unterschied zu der Abfrage im letzten Abschnitt besteht darin, dass diese Abfrage mit einem . beginnt. Dies ist insofern von Bedeutung, als die XPath-Abfrage dadurch auf die einzelne Eigenschaftskarte und nicht auf die gesamte HTML-Datei beschränkt wird.
Extrahieren von Betten, Bädern und Quadratmetern
Nachdem Sie die Preise sortiert haben, fahren Sie mit der Erkundung der HTML-Informationen zu Betten, Bädern und Quadratmetern fort. So sieht das Ganze aus:
Ausgehend von diesem Screenshot sieht es so aus, als gäbe es kein eindeutiges Attribut, das wir anvisieren können. Die Struktur dieser HTML-Datei ist jedoch sehr methodisch und für jede Immobilienliste ähnlich. Zum Glück bietet uns XPath eine Möglichkeit, ein Tag auf der Grundlage des darin enthaltenen Textes zu bestimmen. Sie werden diese Methode verwenden, um die abbr-Tags, die den Text bds, ba und sqft enthalten, zu markieren und dann die numerischen Daten aus dem vorangehenden Geschwister-Tag zu extrahieren.
Es ist wichtig zu wissen, dass nicht jede Immobilienanzeige die Angaben zu Betten, Bädern und Quadratmetern enthält. Daher ist es wichtig, den Code für solche Fälle widerstandsfähig zu machen. Andernfalls wird Selenium eine NoSuchElementException auslösen.
Aktualisieren Sie den Code in app.py, um diese neuen Daten zu extrahieren:
# ...
for property in properties:
property_data = {}
# ...
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'bds')]/preceding-sibling::b"):
property_data['beds'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'bds')]/preceding-sibling::b").text
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'ba')]/preceding-sibling::b"):
property_data['baths'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'ba')]/preceding-sibling::b").text
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'sqft')]/preceding-sibling::b"):
property_data['sqft'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'sqft')]/preceding-sibling::b").text
# ...
Nur weil diese XPaths etwas anders sind als die, die Sie zuvor gesehen haben, lassen Sie uns einen von ihnen dekonstruieren:
.//abbr
: Finden Sie alle Abkürzungen in einer Karte mit Immobilienangeboten[contains(text(), 'bds')]
: die den Text bds enthält/preceding-sibling::b
: und navigieren Sie zu seinem vorhergehenden b-Geschwisterchen
Der Code enthält eine if-Anweisung für jede dieser neuen Daten, da, wie bereits erwähnt, nicht jede Eigenschaftsliste diese Daten enthält. Ohne eine if-Anweisung wird Selenium eine NoSuchElementException auslösen.
Extrahieren der Adresse
Inzwischen kennen Sie den Vorgang bereits. Untersuchen Sie den HTML-Code noch einmal und finden Sie die Tags heraus, die Sie zum Extrahieren der Immobilienadresse verwenden können.
Diese ist einfacher. Jede Immobilienkarte hat ein verschachteltes Adress-Tag, das die eigentliche Adresse enthält:
So sollte der aktualisierte Code aussehen:
# ...
for property in properties:
property_data = {}
# ...
property_data['address'] = property.find_element(By.XPATH, ".//address").text
# ...
Verwendung der Paginierung
Nun, da die eigentliche Datenextraktion geklärt ist, lassen Sie uns darüber nachdenken, wie wir die Paginierung handhaben. Derzeit werden auf jeder Suchergebnisseite nur 40 Ergebnisse angezeigt. Der Rest der Ergebnisse ist hinter einer Paginierung versteckt. Sie finden die Links zur Paginierung unten auf der Ergebnisseite. Sie müssen sich eine Methode ausdenken, mit der Ihr Code automatisch zur nächsten Seite navigiert, nachdem Sie die Immobilienangebote von der aktuellen Seite extrahiert haben.
Wie immer ist dies der Moment, in dem Sie sich mit dem HTML-Code der Seite befassen, um einen cleveren Plan zur Lösung des Problems zu finden. So sieht der HTML-Code für die Paginierung aus:
Der Screenshot hat Ihnen vielleicht schon einen Hinweis darauf gegeben, was Sie als nächstes tun werden. Wie Sie sehen können, gibt es in jedem Paginierungsabschnitt diese Schaltfläche Nächste Seite (>), auf die Sie klicken können, um zur nächsten Seite zu gelangen. Wenn Sie sich auf der letzten Seite befinden, wird das Attribut aria-disabled für diese Schaltfläche auf true gesetzt. Dadurch werden Sie darüber informiert, dass Sie alle Auflistungen extrahiert haben.
Lassen Sie uns den Code in app.py aktualisieren, um die Paginierung zu behandeln. Zunächst müssen Sie den bereits vorhandenen Code aktualisieren und ihn in eine Funktion verpacken. Dadurch wird der Code ein wenig aufgeräumt und eine Verdoppelung des Codes vermieden, wenn Sie die Logik für die Paginierung hinzufügen:
all_properties = []
def extract_properties():
properties = driver.find_elements(By.XPATH, "//article[@data-test="property-card"]")
for property in properties:
property_data = {}
property_data['price'] = property.find_element(By.XPATH,
".//span[@data-test="property-card-price"]").text
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'bds')]/preceding-sibling::b"):
property_data['beds'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'bds')]/preceding-sibling::b").text
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'ba')]/preceding-sibling::b"):
property_data['baths'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'ba')]/preceding-sibling::b").text
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'sqft')]/preceding-sibling::b"):
property_data['sqft'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'sqft')]/preceding-sibling::b").text
property_data['address'] = property.find_element(By.XPATH, ".//address").text
all_properties.append(property_data)
pprint(all_properties)
Fügen Sie nun den folgenden Code am Ende hinzu, um die Paginierung zu behandeln:
from selenium.common.exceptions import NoSuchElementException
# ...
extract_properties()
while True:
try:
next_page_url = driver.find_element(By.XPATH, "//a[@rel="next" and @aria-disabled='false']").get_attribute('href')
driver.get(next_page_url)
except NoSuchElementException:
break
extract_properties()
print(all_properties)
Der Code ist recht einfach. Er verwendet eine XPath-Abfrage, um ein Anker-Tag zu finden, dessen rel-Attribut auf next und das aria-disabled-Attribut auf false gesetzt ist. Solange es eine nächste Seite gibt, wird die Navigation zu dieser Seite fortgesetzt. Wenn er die letzte Seite erreicht, löst der XPath-Ausdruck eine Ausnahme aus, da das Attribut aria-disabled auf true gesetzt ist. An diesem Punkt bricht er die Schleife ab und gibt die extrahierten Daten aus.
Vollständiger Code
Wenn Sie genau verfolgt haben, ist dies der endgültige Code, den Sie erhalten haben sollten:
from pprint import pprint
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
driver = uc.Chrome(use_subprocess=True)
driver.get("https://www.zillow.com/bellevue-wa/")
all_properties = []
def extract_properties():
properties = driver.find_elements(By.XPATH, "//article[@data-test="property-card"]")
for property in properties:
property_data = {}
property_data['price'] = property.find_element(By.XPATH, ".//span[@data-test="property-card-price"]").text
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'bds')]/preceding-sibling::b"):
property_data['beds'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'bds')]/preceding-sibling::b").text
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'ba')]/preceding-sibling::b"):
property_data['baths'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'ba')]/preceding-sibling::b").text
if property.find_elements(By.XPATH, ".//abbr[contains(text(), 'sqft')]/preceding-sibling::b"):
property_data['sqft'] = property.find_element(By.XPATH, ".//abbr[contains(text(), 'sqft')]/preceding-sibling::b").text
property_data['address'] = property.find_element(By.XPATH, ".//address").text
all_properties.append(property_data)
pprint(all_properties)
extract_properties()
while True:
try:
next_page_url = driver.find_element(By.XPATH, "//a[@rel="next" and @aria-disabled='false']").get_attribute('href')
driver.get(next_page_url)
except NoSuchElementException:
break
extract_properties()
print(all_properties)
Der einzige Unterschied in diesem Code ist, dass ich am Ende der for-Schleife einen Aufruf der Funktion pprint hinzugefügt habe. Dies ist nützlich, da es die Eigenschaftsdaten von jeder Seite "hübsch" ausdruckt, sobald sie abgefragt werden.
Versuchen Sie, diesen Code auszuführen und Sie sollten die gescrapten Daten im Terminal sehen. Denken Sie daran, dass Sie auch in eine rotierende Proxy-Lösung ähnlich der ScraperAPI investieren sollten, damit Zillow Ihre IP-Adresse nicht aufgrund übermäßiger Anfragen blockiert.
Fazit
In diesem Artikel haben Sie gelernt, wie Sie Daten von der Suchergebnisseite von Zillow abrufen können. Sie haben gesehen, wie XPaths funktionieren und wie Sie sie auf kreative Art und Weise nutzen können, um HTML-Elemente zu suchen und zu finden. Außerdem haben Sie erfolgreich die Paginierung im Code konfiguriert. Wenn Sie Zillow in großem Umfang scrapen möchten, ist es wichtig, die Grenzen des aktuellen Systems zu kennen. Derzeit ist es für Zillow sehr einfach, Ihre IP-Adresse wegen übermäßiger Anfragen zu finden und zu sperren. Sie können dies leicht umgehen, indem Sie rotierende Proxys von einem zuverlässigen Proxy-Anbieter wie ScraperAPI verwenden. Dadurch wird sichergestellt, dass Ihre persönliche IP-Adresse niemals gesperrt wird und Ihr Scraping-Projekt ohne Probleme weiterlaufen kann.
Wir hier bei Scraper API sind Experten für Scraping und Proxy. Wenn Sie ein Projekt haben, bei dem Sie Hilfe und Unterstützung benötigen, zögern Sie nicht, sich an uns zu wenden.