Apartments.com ist einer der größten Mietmarktplätze im Web, und seine Angebotsseiten enthalten genau die strukturierten Daten, die Mietpreisbenchmarking, Marktforschung und Lead-Generierung im Immobilienbereich antreiben: einen Immobiliennamen und eine Adresse, die monatliche Miete, Schlafzimmer, Badezimmer, Quadratmeterzahl und die Ausstattungsliste. Das Problem ist, dass diese Seiten clientseitig gerendert werden und die Website automatisierten Traffic aggressiv abwehrt, sodass eine einfache HTTP-Anfrage Ihnen eine nahezu leere Hülle statt des gesuchten Angebots liefert.
Dieser Leitfaden zeigt Ihnen, wie Sie Apartments.com mit Python zuverlässig scrapen. Sie bauen einen kleinen, lauffähigen Scraper, der ein gerendertes Angebot über die Crawling API abruft, die gewünschten Felder mit BeautifulSoup parst und saubere strukturierte Ausgaben druckt. Das gesamte Tutorial beschränkt sich auf öffentliche Angebotsdaten, und der Abschnitt zur Rechtslage am Ende ist kein Boilerplate, also lesen Sie ihn, bevor Sie dies auf echtes Volumen loslassen.
Was Sie bauen werden
Ein Python-Skript, das eine öffentliche Apartments.com-Angebots-URL nimmt, das gerenderte HTML über die Crawling API abruft und einen strukturierten Datensatz für die Immobilie extrahiert. Als laufendes Beispiel verwenden wir ein einzelnes Mietangebot und ziehen diese Felder heraus:
- Name und Adresse der Immobilienname und die Straßenadresse.
- Miete die im Angebot angezeigte monatliche Miete oder Mietspanne.
- Schlafzimmer die Anzahl der Schlafzimmer.
- Badezimmer die Anzahl der Badezimmer.
- Größe die Quadratmeterzahl der Einheit.
- Ausstattung die Liste der Merkmale, wie Klimaanlage, Parkplatz oder Waschmaschine in der Wohnung.
Warum ein einfacher Fetch bei Apartments.com scheitert
Wenn Sie eine Apartments.com-Angebots-URL mit einem einfachen HTTP-Client anfragen, erhalten Sie eine Antwort mit Status 200 und fast keinen Angebotsdaten im Body. Zwei Dinge arbeiten gegen Sie. Erstens rendert Apartments.com einen Großteil seines Angebotsinhalts im Browser mit JavaScript, sodass das initiale HTML eine dünne Hülle ist, die sich erst nach dem Ausführen der Seitenscripts füllt. Zweitens markiert die Website automatisierten Traffic schnell: Rechenzentrums-IPs und Anfragemuster, die nicht wie ein echter Browser aussehen, werden herausgefordert oder mit einem Captcha konfrontiert, bevor sie den gerenderten Inhalt erreichen.
Ein funktionierender Apartments.com-Scraper benötigt also zwei Dinge in einer einzigen Anfrage: einen Browser, der die Seite tatsächlich rendert, und eine IP, die die Plattform als echten Besucher liest. Sie können das selbst mit einem Headless-Browser plus einem Pool aus rotierenden Residential Proxys zusammenbauen, aber das Zusammenfügen und Gesundhalten dieser Teile ist der Hauptteil der Arbeit. Die Crawling API fasst beides in einem einzigen Aufruf zusammen: Sie senden die URL mit einem JavaScript-Token, sie rendert die Seite hinter einer vertrauenswürdigen IP und gibt fertiges HTML zum Parsen zurück.
Crawlbase bietet zwei Token-Typen. Das normale Token ruft statisches HTML ab; das JavaScript-Token (JS) rendert die Seite zuerst in einem echten Browser. Apartments.com füllt seine Angebotfelder clientseitig, daher benötigen Sie hier das JS-Token. Das normale Token zu verwenden, gibt dieselbe leere Hülle zurück, die ein einfacher Fetch liefern würde, und es gibt daraus nichts Nützliches zu parsen.
Voraussetzungen
Bevor Sie Code schreiben, müssen einige Dinge eingerichtet sein. Keines davon dauert lange.
Python-Grundkenntnisse. Sie sollten in der Lage sein, ein Python-Skript zu schreiben und auszuführen und Pakete mit pip zu installieren. Wenn Sie neu in der Sprache sind, bringen Sie die offizielle Python-Dokumentation und ein beliebiger Einsteigerkurs auf das Niveau, das dieses Tutorial voraussetzt.
Python 3.8 oder neuer. Überprüfen Sie Ihre Version mit python --version. Falls nicht vorhanden, installieren Sie Python von python.org oder über eine Distribution wie Anaconda.
Ein Crawlbase-Konto und JS-Token. Melden Sie sich an, öffnen Sie Ihr Dashboard und kopieren Sie Ihr JavaScript-Token (JS) von der Kontodokumentationsseite. Behandeln Sie das Token wie ein Passwort: Es authentifiziert Ihre Anfragen, halten Sie es also aus der Versionsverwaltung heraus.
Das Projekt einrichten
Erstellen Sie eine virtuelle Umgebung, damit Projektabhängigkeiten isoliert bleiben, und installieren Sie dann die zwei Bibliotheken, die der Scraper benötigt.
python --version python -m venv apartments_env source apartments_env/bin/activate pip install crawlbase beautifulsoup4
Unter Windows aktivieren Sie die Umgebung mit apartments_env\Scripts\activate statt der source-Zeile. Zwei Abhängigkeiten übernehmen die Arbeit: crawlbase ist der offizielle Client für die Crawling API, und beautifulsoup4 parst das zurückgegebene HTML, sodass Sie einzelne Felder per CSS-Selektor herausziehen können. Wenn Sie den Parser noch nicht verwendet haben, ist der BeautifulSoup-Leitfaden ein guter Begleiter zu diesem Tutorial.
Schritt 1: Das gerenderte Angebot abrufen
Beginnen Sie damit, die fertige Seite zu laden. Importieren Sie die Klasse CrawlingAPI, initialisieren Sie sie mit Ihrem JS-Token und fragen Sie die Angebots-URL ab. Das Prüfen des Status-Codes vor dem Parsen hält Fehler laut statt stumm.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": page_url = "https://www.apartments.com/2630-n-hamlin-ave-chicago-il/kvl7tm9/" html = crawl(page_url) print(html[:500] if html else "No HTML returned")
Die zwei Warte-Optionen sind bei einem clientseitig gerenderten Ziel wie diesem wichtig. ajax_wait weist die API an, auf das Abschließen asynchroner Inhalte zu warten, und page_wait hält nach dem Laden für eine feste Anzahl von Millisekunden an, damit spät rendernde Elemente erscheinen, bevor die Seite erfasst wird. Fünf Sekunden sind ein vernünftiger Ausgangswert; erhöhen Sie diesen Wert, wenn die Angebotfelder leer zurückkommen. Führen Sie das Skript mit python scraper.py aus und Sie sollten echtes Angebots-Markup sehen, nicht die leere Hülle, die ein einfacher Fetch zurückgibt. Das bestätigt, dass das Rendering funktioniert, bevor Sie einen einzigen Selektor schreiben.
Apartments.com benötigt eine gerenderte Seite hinter einer vertrauenswürdigen IP, in einem einzigen Aufruf. Die Crawling API nimmt ein JS-Token, führt die Seite in einem echten Browser aus, rotiert serverseitig durch Residential IPs und gibt fertiges HTML zurück, sodass Sie keinen eigenen Headless-Fleet und keinen Proxy-Pool betreiben müssen. Richten Sie es zuerst auf ein öffentliches Angebot im kostenlosen Tarif aus.
Schritt 2: Die Angebotfelder mit BeautifulSoup parsen
Mit dem gerenderten HTML laden Sie es in BeautifulSoup und ziehen jedes Feld per Selektor heraus. Apartments.com legt die Kern-Angebotdetails in einer vorhersagbaren Struktur an, sodass Sie Name, Miete, Schlafzimmer, Badezimmer, Größe und Ausstattung einzelnen Selektoren zuordnen können. Kapseln Sie die gesamte Extraktion in Helfer, die None oder eine leere Liste zurückgeben, wenn ein Element fehlt, damit ein fehlendes Feld den Lauf nicht zum Absturz bringt.
from bs4 import BeautifulSoup def text_of(soup, selector): el = soup.select_one(selector) return el.get_text(strip=True) if el else None def detail_at(soup, index): rows = soup.select(".rentInfoDetail") return rows[index].get_text(strip=True) if index < len(rows) else None def scrape_listing(html): soup = BeautifulSoup(html, "html.parser") address = soup.select_one(".propertyAddress") location = ", ".join( s.get_text(strip=True) for s in address.select("span") ) if address else None amenities = [ s.get_text(strip=True) for s in soup.select("#amenitiesSection .specInfo span") ] return { "name": text_of(soup, "#propertyName"), "location": location, "rent": detail_at(soup, 0), "beds": detail_at(soup, 1), "baths": detail_at(soup, 2), "size": detail_at(soup, 3), "amenities": amenities, }
Die Helfer text_of und detail_at erledigen dasselbe nützliche Ding in zwei Formen: Sie fragen ein Element ab und geben None zurück, wenn es fehlt, statt bei einem Aufruf gegen nichts zu werfen. Die Adresse wird durch Verbinden des Texts jedes span innerhalb von .propertyAddress aufgebaut, da Apartments.com Straße, Stadt und Bundesstaat in separate Elemente aufteilt. Ausstattungen kommen als Liste zurück, weil ein Angebot zwischen null und Dutzenden haben kann. Diese Struktur macht die Extraktion robust, wenn ein Feld bei einem bestimmten Angebot fehlt, was häufig vorkommt, da nicht jede Immobilie eine Größe oder eine vollständige Ausstattungsliste angibt.
Apartments.com-Klassennamen (die rentInfoDetail-Zeilen, der #amenitiesSection-Wrapper, die Adress-Spans) ändern sich ohne Vorankündigung. Behandeln Sie die obigen Selektoren als Ausgangssvorlage, nicht als Vertrag. Wenn ein Feld als None oder eine leere Liste zurückkommt, untersuchen Sie das Live-Angebot in den Dev-Tools Ihres Browsers und aktualisieren Sie den Selektor. Periodische Selektor-Wartung ist bei jedem Produktions-Scraper normal, kein Zeichen dafür, dass etwas kaputt ist.
Schritt 3: Alles zusammenfügen
Verbinden Sie nun den Fetch und das Parsen zu einem lauffähigen Skript. Rufen Sie das gerenderte HTML ab, übergeben Sie es an den Parser und geben Sie den strukturierten Datensatz aus.
import json from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None def text_of(soup, selector): el = soup.select_one(selector) return el.get_text(strip=True) if el else None def detail_at(soup, index): rows = soup.select(".rentInfoDetail") return rows[index].get_text(strip=True) if index < len(rows) else None def scrape_listing(html): soup = BeautifulSoup(html, "html.parser") address = soup.select_one(".propertyAddress") location = ", ".join( s.get_text(strip=True) for s in address.select("span") ) if address else None amenities = [ s.get_text(strip=True) for s in soup.select("#amenitiesSection .specInfo span") ] return { "name": text_of(soup, "#propertyName"), "location": location, "rent": detail_at(soup, 0), "beds": detail_at(soup, 1), "baths": detail_at(soup, 2), "size": detail_at(soup, 3), "amenities": amenities, } def main(): page_url = "https://www.apartments.com/2630-n-hamlin-ave-chicago-il/kvl7tm9/" html = crawl(page_url) if not html: return data = scrape_listing(html) print(json.dumps(data, indent=2)) if __name__ == "__main__": main()
Wie die Ausgabe aussieht
Führen Sie das vollständige Skript mit python scraper.py aus und Sie erhalten einen sauberen strukturierten Datensatz für das Angebot, bereit zum Schreiben in JSON, CSV oder eine Datenbank.
{ "name": "2630 N Hamlin Ave", "location": "2630 N Hamlin Ave, Chicago, IL, 60647", "rent": "$2,350", "beds": "2 bd", "baths": "1 ba", "size": "1,000 sq ft", "amenities": ["Air Conditioning", "Dishwasher", "Basement", "Laundry Facilities"] }
Über Angebote und Paginierung skalieren
Ein Angebot ist ein Demo; ein echter Job läuft über eine ganze Suche. Apartments.com paginiert seine Suchergebnisse, sodass das Muster zwei Ebenen hat: crawlen Sie jede Suchergebnisseite, um die Angebots-URLs zu sammeln, dann rufen Sie jedes Angebot über dieselbe Funktion ab, die Sie bereits geschrieben haben. Da jedes Angebot dieselbe Struktur hat, funktioniert der Parser für alle ohne Änderungen.
import time def collect_listing_urls(search_html): soup = BeautifulSoup(search_html, "html.parser") cards = soup.select("article.placard a.property-link") return [a["href"] for a in cards if a.get("href")] def scrape_search(base_url, pages): listings = [] for page in range(1, pages + 1): search_html = crawl(f"{base_url}{page}/") if not search_html: continue for url in collect_listing_urls(search_html): html = crawl(url) if html: listings.append(scrape_listing(html)) time.sleep(2) return listings results = scrape_search("https://www.apartments.com/chicago-il/", pages=3) with open("listings.json", "w") as f: json.dump(results, f, indent=2)
Apartments.com hängt die Seitennummer an den Suchpfad an, sodass das Iterieren von page das Ergebnisset durchläuft. Das time.sleep(2) zwischen Angebots-Fetches ist bewusst: Es bremst den Lauf, damit Sie die Website nicht hämmern, was die wirksamste einzelne Gewohnheit ist, um nicht gesperrt zu werden. Passen Sie die Seitenanzahl und den Stadt-Slug an Ihr Ziel an.
Nicht gesperrt bleiben
Selbst mit behandeltem Rendering achtet Apartments.com auf Scraper-ähnlichen Traffic. Einige Gewohnheiten halten einen Lauf gesund, und sie gelten für jedes schwierige kommerzielle Ziel.
-
Drosseln Sie Ihre Anfragen. Angebote in einer engen Schleife zu hämmern ist der schnellste Weg, gedrosselt oder mit einem Captcha konfrontiert zu werden. Verteilen Sie Anfragen, wie das obige
sleepes tut, und variieren Sie Ihre Ziele statt einen Pfad mit voller Geschwindigkeit zu crawlen. - Setzen Sie auf Rotation. Ein Pool von Residential IPs verteilt Anfragen über viele echte Nutzer-Adressen, sodass keine einzelne ein Rate-Limit auslöst. Die Crawling API erledigt das für Sie; wenn Sie Ihren eigenen Stack aufbauen, ist das der Teil, den Sie richtig machen müssen.
- Lesen Sie die Status-Codes. Ein Lauf, der beginnt, Herausforderungen oder Fehler zurückzugeben, teilt Ihnen mit, dass die aktuelle Rate oder IP-Tier nicht mehr ausreicht. Behandeln Sie das als Signal zum Zurückziehen, nicht als Rauschen, das ignoriert werden soll.
Das breitere Spielbuch finden Sie in Wie man Websites scrapt, ohne gesperrt zu werden und im tieferen Einblick in Wie man CAPTCHAs beim Web-Scraping umgeht. Wenn Sie Ihren eigenen Traffic lieber durch einen rotierenden Pool leiten möchten, statt die verwaltete API zu verwenden, bietet der Smart AI Proxy (auch AI Proxy genannt) dieselbe Residential-IP-Rotation als Drop-in-Proxy-Endpunkt.
Ist es legal, Apartments.com zu scrapen?
Ob das Scrapen von Apartments.com erlaubt ist, hängt von den Nutzungsbedingungen von Apartments.com, Ihrer Rechtsprechung und der Verwendung der Daten ab. Die Nutzungsbedingungen beschränken den automatisierten Zugriff, sodass das Scrapen gegen diese Bedingungen verstoßen kann, unabhängig davon, wie sorgfältig Ihr Tooling ist. Kein Code hier ändert das; er macht nur den technischen Teil funktionieren. Lesen Sie die Nutzungsbedingungen von Apartments.com und seine robots.txt und behandeln Sie beide als Grenze dessen, was Sie sammeln.
Einige Linien, die es wert sind einzuhalten. Sammeln Sie nur öffentliche Angebotsdaten: den Immobiliennamen und die Adresse, die Miete, Schlafzimmer, Badezimmer, Quadratmeterzahl und die öffentlich gezeigte Ausstattungsliste, die jeder ohne Konto sehen kann. Respektieren Sie die angegebenen Rate-Erwartungen von Apartments.com und halten Sie Ihr Anfragevolumen niedrig genug, um die Server nicht zu belasten. Vermeiden Sie alles, was mit identifizierbaren Personen zusammenhängt, einschließlich der Kontaktdaten von Vermietern, Agenten oder Immobilienverwaltern, die auf einer Seite aufgelistet sind. Wenn Sie die Daten kommerziell oder in großem Umfang wiederverwenden möchten, holen Sie eine Erlaubnis oder eine offizielle Vereinbarung ein, statt Schweigen als Zustimmung zu werten.
Dieser Leitfaden beschränkt sich bewusst auf öffentliche Angebotsseiten, weil das die Linie ist, die die Arbeit vertretbar hält. Er deckt nichts hinter einem Login ab, keine gespeicherten Such- oder Kontodaten, keine persönlichen oder Kontaktdaten von Einzelpersonen, keine login-geschützten Seiten und keinen Versuch, Authentifizierung zu umgehen. Nur öffentliche Angebotsdaten. Wenn Ihr Projekt mehr als das benötigt, ist eine Lizenzvereinbarung oder ein Immobiliendatenanbieter der richtige Weg, kein cleverer Scraper.
Wichtigste Erkenntnisse
- Apartments.com wird clientseitig gerendert. Ein einfacher Fetch gibt eine leere Hülle zurück, Sie müssen die Seite daher vor dem Parsen rendern.
-
Sie benötigen Rendering und eine vertrauenswürdige IP zusammen. Die Crawling API mit einem JS-Token erledigt beides in einem Aufruf;
ajax_waitundpage_waitsteuern, wie lange auf Inhalte gewartet wird. - BeautifulSoup übernimmt die Extraktion. Ordnen Sie Name, Adresse, Miete, Schlafzimmer, Badezimmer, Größe und Ausstattung aktuellen Selektoren zu und erwarten Sie, dass diese Selektoren driften.
- Skalieren Sie durch Paginierung der Suche, dann Schleifen über Angebote. Sammeln Sie URLs von jeder Ergebnisseite, rufen Sie jedes Angebot mit demselben Parser ab und bremsen Sie den Lauf mit einem kurzen Sleep.
- Bleiben Sie bei öffentlichen Daten. Respektieren Sie Apartments.com's Nutzungsbedingungen und robots.txt, sammeln Sie nur öffentliche Angebotfelder und berühren Sie niemals Konten, Logins oder persönliche Kontaktdaten von Einzelpersonen.
Häufig gestellte Fragen
Warum gibt ein einfacher Fetch keine Daten von Apartments.com zurück?
Weil Apartments.com seinen Angebotinhalt clientseitig mit JavaScript rendert. Das initiale HTML ist eine Hülle, die sich erst füllt, nachdem die Seitenscripts in einem Browser ausgeführt wurden, sodass eine rohe HTTP-Anfrage Status 200 mit leeren Miete-, Schlafzimmer-, Badezimmer- und Ausstattungsfeldern zurückgibt. Um echte Daten zu erhalten, müssen Sie die Seite zuerst rendern, was das JS-Token der Crawling API für Sie erledigt.
Benötige ich das normale Token oder das JS-Token für Apartments.com?
Das JS-Token. Das normale Token ruft statisches HTML ab, das bei Apartments.com dieselbe leere Hülle ist, die ein einfacher Fetch zurückgibt. Das JS-Token rendert die Seite in einem echten Browser, bevor das HTML zurückgegeben wird, sodass die Angebotfelder vorhanden sind, wenn BeautifulSoup sie parst.
Welche Daten kann ich von einem Apartments.com-Angebot scrapen?
Öffentliche Angebotfelder: den Immobiliennamen und die Straßenadresse, die monatliche Miete oder Mietspanne, die Anzahl der Schlafzimmer und Badezimmer, die Quadratmeterzahl und die Ausstattungsliste. Bleiben Sie bei Daten, die für jeden Besucher ohne Konto sichtbar sind, und vermeiden Sie die persönlichen Kontaktdaten von Vermietern, Agenten oder Immobilienverwaltern, die außerhalb des in diesem Leitfaden abgedeckten öffentlichen Angebots-Bereichs liegen.
Meine Selektoren geben None oder eine leere Liste zurück. Was hat sich geändert?
Fast sicher das Markup von Apartments.com. Die rentInfoDetail-Zeilen, der #amenitiesSection-Wrapper und die Adress-Spans ändern sich ohne Vorankündigung, sodass Selektoren, die letzten Monat funktioniert haben, jetzt kaputt sein können. Untersuchen Sie ein Live-Angebot in den Dev-Tools Ihres Browsers und aktualisieren Sie die Selektoren. Periodische Selektor-Wartung ist bei jedem Produktions-Scraper normal.
Wie gehe ich mit der Paginierung über die Angebote einer Stadt um?
Apartments.com hängt die Seitennummer an den Suchpfad an, sodass Sie jede Ergebnisseite der Reihe nach crawlen, die Angebots-Links von den Karten darauf sammeln und jedes Angebot mit demselben Parser abrufen. Fügen Sie zwischen Anfragen einen kurzen Sleep ein und hören Sie auf, wenn eine Seite keine neuen Karten mehr zurückgibt. Die obige Funktion scrape_search zeigt die vollständige Schleife.
Wie vermeide ich Sperren beim Scrapen von Apartments.com?
Halten Sie Ihre Per-IP-Anfragerate niedrig, bremsen Sie Anfragen mit einer kurzen Verzögerung, variieren Sie Ihre Ziele statt einen Pfad zu schleifen und routen Sie durch rotierende Residential IPs, damit keine einzelne Adresse ein Rate-Limit auslöst. Die Crawling API verwaltet Rotation und einen vertrauenswürdigen IP-Pool für Sie; wenn Sie Ihren eigenen Stack aufbauen, ist das der Teil, in den Sie investieren sollten. Achten Sie auf die Status-Codes und ziehen Sie sich zurück, wenn Herausforderungen beginnen.
Crawlen Sie jede Website im großen Maßstab, ohne gegen die Infrastruktur zu kämpfen.
Crawlbase übernimmt Proxys, Fingerprints und CAPTCHAs, damit Ihr Team Datenpipelines ausliefert, statt Crawl-Infrastruktur zu pflegen. 1.000 Anfragen kostenlos, keine Karte erforderlich.
