Immobilien-Scraper selbst gebaut: Schritt-für-Schritt-Anleitung zum Sammeln von Objekten, Preisen und Preisverlauf
Inhalt des Artikels
- Einführung: was sie am ende haben werden
- Vorbereitung: tools und umgebung
- Grundbegriffe: was sie vorher verstehen sollten
- Schritt 1: ziel und datenstruktur festlegen
- Schritt 2: die struktur der ergebnisseite untersuchen
- Schritt 3: den ersten scraper für die ergebnisseite schreiben
- Schritt 4: objekt-detailseiten sammeln
- Schritt 5: mobile proxys einbinden und die sammlung stabil machen
- Schritt 6: daten speichern und preisverlauf aufbauen
- Schritt 7: automatischen start einrichten und auf andere plattformen erweitern
- Ergebnisprüfung: checkliste für einen fertigen scraper
- Typische fehler und lösungen
- Zusätzliche möglichkeiten für fortgeschrittene
- Faq: häufige fragen zur erstellung eines immobilien-scrapers
- Fazit
Einführung: Was Sie am Ende haben werden
Der Immobilienmarkt lebt von Zahlen. Die einen suchen eine Wohnung unter Marktwert, die anderen analysieren Wettbewerber in Neubauprojekten, wieder andere erstellen Berichte für Investoren. Eines haben sie gemeinsam: Sie brauchen aktuelle Daten zu Objekten und Preisen, und manuell ist das nicht zu schaffen. Genau hier kommt ein eigener Immobilien-Scraper ins Spiel.
In dieser Anleitung bauen Sie von Grund auf ein funktionierendes Tool, das drei Dinge kann. Erstens: Ergebnisseiten nach einem bestimmten Filter durchlaufen und eine Liste von Objekten mit Preis, Adresse, Fläche und Link sammeln. Zweitens: die Detailseite jedes Objekts aufrufen und Details wie Etage, Baujahr und Haustyp extrahieren. Drittens, das Wertvollste: Daten in einer Datenbank speichern und Tag für Tag einen Preisverlauf aufbauen, sodass Sie sehen, welche Objekte günstiger geworden sind, welche vom Markt genommen wurden und wie sich der Markt in einem bestimmten Viertel entwickelt.
Das Endergebnis sieht so aus: Sie haben einen Ordner mit Python-Skripten, eine SQLite-Datenbankdatei und eine Tabelle, die Sie in Excel oder Google Sheets öffnen können. Sie starten das Skript morgens und erhalten einen frischen Überblick. Nach einer Woche haben Sie bereits Trends.
Für wen dieser Leitfaden ist
- Für Marketing- und Analyseexperten von Immobilienagenturen, die Preisüberwachung in Stadtteilen benötigen, ohne teure Berichte zu kaufen.
- Für Arbitrageure und Unternehmer, die Nischen suchen und Angebot und Nachfrage in Zahlen verstehen wollen.
- Für angehende Entwickler, die Scraping an einem lebendigen und verständlichen Beispiel lernen möchten.
- Für Investoren und private Käufer, die Objekte mit Preissenkungen früher als andere entdecken wollen.
Was Sie vorher wissen sollten
Programmiererfahrung ist nicht erforderlich. Wir erklären jede Codezeile und warum sie nötig ist. Es reicht, wenn Sie Programme installieren, die Kommandozeile öffnen und Text kopieren können. Wenn Sie schon einmal die Entwicklertools im Browser geöffnet haben, wird es ganz leicht. Falls nicht, zeigen wir Ihnen, wo sie zu finden sind.
Die einzige Voraussetzung: Aufmerksamkeit. Scraping reagiert empfindlich auf Tippfehler in Klassennamen und URLs. Ein zusätzlicher Buchstabe, und das Skript gibt eine leere Liste zurück. Keine Sorge: Jeder Schritt hat einen Prüfpunkt, an dem Sie sich vergewissern können, dass alles nach Plan läuft.
Wie viel Zeit Sie brauchen
Die Vorbereitung der Umgebung dauert etwa 30 Minuten. Den ersten funktionierenden Scraper für die Ergebnisseite schreiben Sie in einer Stunde. Objekt-Detailseiten, Proxys und die Datenbank erfordern weitere anderthalb bis zwei Stunden. Insgesamt also drei bis vier Stunden reine Zeit, wenn Sie es ohne Eile angehen. Der Preisverlauf beginnt sich ab dem zweiten Lauf von selbst aufzubauen.
Vorbereitung: Tools und Umgebung
Bevor wir mit dem Code beginnen, sammeln wir alles Nötige. Diesen Abschnitt sollten Sie nicht überspringen: Die Hälfte der Probleme bei Anfängern entsteht durch falsch installiertes Python oder fehlende Bibliotheken.
Systemanforderungen
- Ein Computer mit Windows 10 oder 11, macOS oder Linux. Jedes Laptop der letzten acht Jahre reicht aus.
- Mindestens 4 GB RAM. Für die Variante mit Browser-Automatisierung sind 8 GB empfehlenswert.
- Etwa 2 GB freier Speicherplatz für Python, Bibliotheken und die Datenbank.
- Eine stabile Internetverbindung.
Was Sie installieren müssen
- Python 3.11 oder neuer. Laden Sie das Installationsprogramm von der offiziellen Website python.org herunter. Unter Windows setzen Sie bei der Installation unbedingt das Häkchen bei Add Python to PATH unten auf dem ersten Bildschirm. Ohne das funktioniert der Befehl python im Terminal nicht. Unter macOS ist Python oft schon vorhanden, aber besser ist eine aktuelle Version.
- Code-Editor. Wir empfehlen Visual Studio Code: kostenlos, Syntaxhervorhebung und Fehleranzeige. Installieren Sie die Python-Erweiterung aus dem integrierten Erweiterungsmarkt (Symbol mit vier Quadraten in der linken Leiste).
- Browser Chrome oder Edge. Wir benötigen die Entwicklertools, um die Struktur der Seiten zu untersuchen.
- Python-Bibliotheken. Diese installieren wir weiter unten über das Terminal.
- Zugang zu mobilen Proxys. Wird im fünften Schritt benötigt. Sie brauchen Serveradresse, Port, Benutzername, Passwort und einen Link zum Wechseln der IP-Adresse. All das erhalten Sie im Kundenbereich des Anbieters nach dem Kauf. Falls Sie noch keine Proxys haben, können die ersten Schritte auch ohne sie ausgeführt werden.
Arbeitsordner und virtuelle Umgebung erstellen
- Erstellen Sie auf der Festplatte einen Ordner namens cian_parser. Vermeiden Sie russische Buchstaben und Leerzeichen im Pfad: Sie brechen manchmal Tools.
- Öffnen Sie das Terminal. Unter Windows drücken Sie Win+R, geben cmd ein und drücken Enter. Unter macOS öffnen Sie Terminal über Spotlight.
- Wechseln Sie mit cd und dem Pfad in den Ordner, zum Beispiel:
cd C:\projects\cian_parserunter Windows odercd ~/projects/cian_parserunter macOS. - Erstellen Sie eine virtuelle Umgebung mit dem Befehl
python -m venv venv. Das ist eine isolierte Python-Kopie, damit die Projektbibliotheken nicht mit Systembibliotheken kollidieren. - Aktivieren Sie die Umgebung. Unter Windows:
venv\Scripts\activate. Unter macOS und Linux:source venv/bin/activate. Am Anfang der Terminalzeile erscheint (venv). - Installieren Sie die Bibliotheken mit einem Befehl:
pip install requests beautifulsoup4 lxml pandas openpyxl. Die Installation dauert ein bis zwei Minuten.
Prüfung: Geben Sie im Terminal python -c "import requests, bs4, pandas; print('ok')" ein. Wenn das Wort ok ohne Fehler erscheint, ist die Umgebung bereit. Sehen Sie ModuleNotFoundError, ist die Umgebung nicht aktiviert oder die Installation wurde abgebrochen. Aktivieren Sie venv erneut und wiederholen Sie pip install.
Backups
In diesem Projekt ist nicht der Code das Wertvollste, sondern die gesammelte Datenbank mit dem Preisverlauf. Sie lässt sich nicht wiederherstellen: Vergangene Preise tauchen nirgendwo wieder auf. Deshalb sollten Sie sich von Anfang an angewöhnen: Die Datenbankdatei wird mindestens einmal pro Woche in die Cloud oder auf eine externe Festplatte kopiert. Später fügen wir automatisches Kopieren in das Skript ein.
Grundbegriffe: Was Sie vorher verstehen sollten
Wir klären die Begriffe, die später auftauchen. Wenn Sie mit Scraping schon vertraut sind, überfliegen Sie den Abschnitt, aber beachten Sie den rechtlichen Teil.
Wichtige Begriffe einfach erklärt
- Scraping (Parsen): Automatisches Abrufen einer Webseite durch ein Programm und Extrahieren der benötigten Daten. Dasselbe, was Sie mit den Augen tun, aber ein Skript erledigt es tausendmal schneller.
- HTML: Die Auszeichnungssprache, aus der jede Webseite besteht. Der Preis einer Wohnung auf einem Immobilienportal liegt in einem HTML-Tag mit bestimmten Attributen, und unsere Aufgabe ist es, dieses Tag zu finden.
- Selektor: Die Adresse eines Elements innerhalb des HTML. Zum Beispiel ein span mit dem Attribut data-mark gleich MainPrice. Anhand des Selektors weiß der Scraper, woher er den Preis nehmen soll.
- HTTP-Anfrage: Eine Anfrage an den Server der Website. Der Browser sendet sie, wenn Sie eine Seite öffnen. Die Bibliothek requests macht dasselbe aus dem Code heraus.
- Request-Header: Dienstinformationen, die der Browser mit der Anfrage sendet: Browsertyp, Sprache, Datenformate. Der Server entscheidet danach, was er zurückgibt.
- Proxy: Ein Zwischenserver, über den Ihre Anfragen laufen. Mobile Proxys nutzen IP-Adressen von Mobilfunkbetreibern und ermöglichen den Adresswechsel per Befehl.
- Paginierung: Die Aufteilung der Ergebnisse in Seiten. Um alle Objekte zu erfassen, muss der Scraper die Seiten von der ersten bis zur letzten durchlaufen.
- SQLite: Eine leichte Datenbank in einer Datei. Kein Server nötig, in Python integriert. Ideal für Preisverläufe.
Wie die Ergebnisseiten auf Immobilienportalen aufgebaut sind
CIAN, Domclick, Yandex Nedvizhimost und andere Plattformen funktionieren nach ähnlichem Prinzip. Es gibt eine Suchseite mit Filtern: Stadt, Transaktionsart, Zimmeranzahl, Preisspanne. Jeder Filter wird zu einem Parameter in der Adresszeile. Zum Beispiel bedeutet der Parameter deal_type mit dem Wert sale Verkauf, und room1 gleich 1 fügt Einzimmerwohnungen hinzu. Das Verständnis dieser Parameter gibt Ihnen ein mächtiges Werkzeug: Statt auf der Website zu klicken, formen Sie einfach die gewünschte Adresse.
Innerhalb der Ergebnisse wird jedes Objekt als Karte dargestellt: Titel, Preis, Adresse, einige Fotos, Link zur Detailseite. Die Detailseite enthält vollständige Merkmale und dupliziert oft alle Daten in einem versteckten JSON-Block, den die Website zur Darstellung der Oberfläche verwendet. Diesen Block zu parsen ist viel bequemer als HTML.
Rechtliche und ethische Grenzen
Achtung: Sammeln Sie nur öffentlich zugängliche Informationen über Objekte: Preis, Fläche, Adresse, Gebäudemerkmale. Sammeln und speichern Sie keine Telefonnummern, Namen und andere personenbezogene Daten von Verkäufern und Maklern: Das ist gesetzlich reguliert, und Verstöße können echte Konsequenzen haben. Lesen Sie die Nutzungsbedingungen der Plattform, bevor Sie beginnen, und verwenden Sie die Daten für Ihre eigene Analyse, nicht für Weiterverkauf oder das Erstellen einer Website-Kopie. Halten Sie eine vernünftige Anfragefrequenz ein: Ihr Scraper darf keine Last erzeugen, die den Dienst beeinträchtigt.
Dieser Ansatz ist nicht nur legal, sondern auch praktisch. Ein sorgfältiger Scraper mit Pausen und Adressrotation funktioniert monatelang, während ein aggressiver schon nach einer Stunde temporäre Einschränkungen erhält.
Schritt 1: Ziel und Datenstruktur festlegen
Ziel dieses Schritts: Klar beschreiben, was genau wir sammeln und wie es gespeichert wird. Ohne diesen Schritt schreiben Sie einen Scraper, der alles Mögliche zieht, und verbringen dann eine Woche damit, sich in einem Datenchaos zurechtzufinden.
- Formulieren Sie die Geschäftsfrage. Beispiele: Welche Einzimmerwohnungen in Sankt Petersburg sind im letzten Monat um mehr als 5 Prozent günstiger geworden; wie viel kostet der Quadratmeter in Neubauten eines bestimmten Viertels; wie schnell verschwinden Objekte unter einem bestimmten Preis.
- Legen Sie den Suchfilter fest. Für dieses Beispiel nehmen wir: Verkauf, Bestandsimmobilien, Ein- und Zweizimmerwohnungen, Moskau, Preis bis 15 Millionen Rubel. Sie setzen Ihre eigenen Parameter ein.
- Erstellen Sie eine Liste der Felder. Für jedes Objekt benötigen wir: eindeutige Anzeigen-ID, Link, Titel, Preis, Adresse, Gesamtfläche, Etage und Etagenzahl, Haustyp, Baujahr, Datum der ersten Entdeckung, Datum der letzten Prüfung. Für den Preisverlauf: Anzeigen-ID, Datum, Preis.
- Öffnen Sie den Code-Editor und erstellen Sie im Projektordner die Datei config.py. Schreiben Sie die Parameter hinein, die wir am häufigsten ändern werden:
BASE_URL = 'https://www.cian.ru/cat.php'
SEARCH_PARAMS = {'deal_type': 'sale', 'engine_version': 2, 'offer_type': 'flat', 'region': 1, 'room1': 1, 'room2': 1, 'maxprice': 15000000}
MAX_PAGES = 5
PAUSE_MIN = 4
PAUSE_MAX = 9
DB_PATH = 'realty.db'Beachten Sie: Im Beispielcode sind Zeilenumbrüche durch neue Zeilen dargestellt; im Editor schreiben Sie einfach jede Variable in eine neue Zeile. Der Parameter region gleich 1 entspricht Moskau, 2 entspricht Sankt Petersburg. Die Codes anderer Regionen finden Sie, indem Sie den Filter auf der Website anwenden und die Adresszeile betrachten.
Tipp: Beginnen Sie mit MAX_PAGES gleich 2-3. Auf jeder Ergebnisseite sind etwa 28 Objekte, für die Fehlersuche reicht das. Starten Sie die vollständige Sammlung erst, wenn Sie sicher sind, dass alle Felder korrekt extrahiert werden.
Prüfung: Sie haben die Datei config.py, und in Ihrem Notizblock oder Kopf ist eine Liste von 12 Feldern und eine konkrete Geschäftsfrage festgehalten. Wenn die Frage lautet „Ich will alle Daten für ganz Russland“, gehen Sie zurück und schränken Sie sie ein: Die vollständige Sammlung für das ganze Land umfasst Hunderttausende Objekte und eine völlig andere Infrastruktur.
Mögliche Probleme
Sie können nicht herausfinden, welcher Parameter für den gewünschten Filter verantwortlich ist. Lösung: Öffnen Sie die Website, setzen Sie den Filter manuell, kopieren Sie die Adresse aus der Adresszeile und zerlegen Sie sie an den Ampersand-Zeichen. Jedes Paar Schlüssel gleich Wert ist ein Parameter.
Schritt 2: Die Struktur der Ergebnisseite untersuchen
Ziel dieses Schritts: Die HTML-Elemente finden, aus denen wir Preis, Titel, Adresse und Link entnehmen. Dies ist der forschendste Schritt, und genau hier verlieren Anfänger oft den Faden, also gehen wir ganz langsam vor.
- Öffnen Sie den Browser und rufen Sie die Ergebnisseite des Immobilienportals mit Ihren Filtern auf. Stellen Sie sicher, dass Sie die Wohnungsliste sehen.
- Bewegen Sie den Mauszeiger auf den Preis einer beliebigen Wohnung, klicken Sie mit der rechten Maustaste und wählen Sie Untersuchen (in Edge heißt es „Inspizieren“). Die Entwicklertools öffnen sich, und das Element mit dem Preis wird hervorgehoben.
- Betrachten Sie die hervorgehobene Zeile. Zum Zeitpunkt dieser Anleitung ist es ein span-Tag mit dem Attribut data-mark gleich MainPrice. Notieren Sie dieses Attribut: Es wird der Selektor für den Preis.
- Gehen Sie im Elementbaum nach oben, indem Sie auf übergeordnete Tags klicken, bis Sie ein Tag finden, das die gesamte Objektkarte umfasst. Normalerweise ist das ein article mit dem Attribut data-name gleich CardComponent. Wenn Sie es in der Leiste mit der Maus berühren, wird auf der Seite die gesamte Karte mit Foto und Preis hervorgehoben.
- Finden Sie innerhalb der Karte den Titel (span mit data-mark gleich OfferTitle), die Adresse (mehrere Links a mit data-name gleich GeoLabel, aus denen sich die Adresse zusammensetzt) und den Link zum Objekt (a-Tag mit href, das auf eine Adresse wie cian.ru/sale/flat/Nummer führt). Notieren Sie alle vier Selektoren.
- Finden Sie den Paginierungsblock unten auf der Seite. Scrollen Sie bis zum Ende der Ergebnisse, klicken Sie mit der rechten Maustaste auf die Nummer der zweiten Seite und schauen Sie, wie ihre Adresse aussieht. Sie werden den Parameter p gleich 2 sehen. Das bedeutet, für den Seitenwechsel reicht es, diesen Parameter zu ändern.
Achtung: Die Namen der Attribute data-mark und data-name ändern sich auf den Plattformen bei Design-Updates regelmäßig. Kopieren Sie die Selektoren nicht blind aus diesem Text: Vergleichen Sie sie unbedingt mit der realen Seite in den Entwicklertools. Die Fähigkeit, einen Selektor selbst zu finden, ist wichtiger als jede fertige Liste.
Prüfen, ob verstecktes JSON vorhanden ist
Viele Plattformen speichern die Ergebnisdaten fertig im script-Tag. Das ist bequemer als HTML: Man muss die Adresse nicht aus Fragmenten zusammensetzen.
- Drücken Sie in den Entwicklertools Strg+F (auf macOS Cmd+F) und geben Sie das Wort offers oder initialState ein.
- Wenn die Suche ein script-Tag mit großem Text findet, der wie ein Wörterbuch mit geschweiften Klammern aussieht, dann sind die Daten im JSON vorhanden. Merken Sie sich den Variablennamen am Anfang dieses Blocks.
- Wenn nichts gefunden wird, ist das nicht schlimm: Der HTML-Ansatz aus dem nächsten Schritt funktioniert in jedem Fall.
Tipp: Öffnen Sie den Tab Network (Netzwerk) in den Entwicklertools, aktualisieren Sie die Seite und filtern Sie die Anfragen nach Typ Fetch/XHR. Manchmal lädt die Website die Ergebnisse mit einer separaten Anfrage im JSON-Format. Wenn Sie eine solche Anfrage mit dem Feld offers sehen, ist das Parsen am einfachsten: Sie erhalten reine Daten ohne HTML.
Prüfung: Sie haben Selektoren für Karte, Preis, Titel, Adresse und Link notiert und kennen den Namen des Paginierungsparameters. Beim Klicken auf jeden Selektor in der Leiste sehen Sie die Hervorhebung des gewünschten Elements auf der Seite.
Mögliche Probleme
Die Entwicklertools zeigen HTML, aber der Preis fehlt. Ursache: Die Website rendert einen Teil der Daten per Skript nach dem Laden. Lösung: Prüfen Sie im Tab Network, ob der Preis mit einer separaten Anfrage kommt, oder verwenden Sie die Browser-Automatisierung aus dem Abschnitt für Fortgeschrittene.
Schritt 3: Den ersten Scraper für die Ergebnisseite schreiben
Ziel dieses Schritts: Ein Skript erhalten, das die Ergebnisseite herunterlädt, die Objektliste extrahiert und in der Konsole ausgibt. Nach diesem Schritt haben Sie ein funktionierendes Grundgerüst, auf dem wir die Funktionen aufbauen.
- Erstellen Sie im Projektordner die Datei parser.py.
- Importieren Sie am Anfang der Datei die Bibliotheken und Einstellungen:
import time
import random
import requests
from bs4 import BeautifulSoup
from config import BASE_URL, SEARCH_PARAMS, MAX_PAGES, PAUSE_MIN, PAUSE_MAX- Beschreiben Sie die Request-Header. Der Server sollte darin einen normalen Browser mit russischer Lokalisierung sehen, sonst erhalten Sie möglicherweise nicht die richtige Seitenversion:
HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0 Safari/537.36', 'Accept-Language': 'ru-RU,ru;q=0.9', 'Accept': 'text/html,application/xhtml+xml'}- Schreiben Sie eine Funktion zum Laden der Seite. Sie nimmt die Seitennummer, fügt sie zu den Parametern hinzu und gibt HTML zurück. Wir prüfen unbedingt den Antwortcode: 200 bedeutet Erfolg, alles andere ist ein Signal, anzuhalten und nachzuforschen:
def fetch_page(page, session):
params = dict(SEARCH_PARAMS)
params['p'] = page
resp = session.get(BASE_URL, params=params, headers=HEADERS, timeout=30)
if resp.status_code != 200:
print('Status', resp.status_code, 'auf Seite', page)
return None
return resp.text- Schreiben Sie eine Parsing-Funktion. Sie findet alle Karten und extrahiert für jede die Felder. Achten Sie auf die if-Konstruktion: Wenn ein Element fehlt, stürzen wir nicht ab, sondern schreiben None:
def parse_cards(html):
soup = BeautifulSoup(html, 'lxml')
cards = soup.select('article[data-name=CardComponent]')
result = []
for card in cards:
link_tag = card.select_one('a[href*=/sale/flat/]')
price_tag = card.select_one('span[data-mark=MainPrice]')
title_tag = card.select_one('span[data-mark=OfferTitle]')
geo_tags = card.select('a[data-name=GeoLabel]')
if not link_tag or not price_tag:
continue
url = link_tag.get('href')
offer_id = url.rstrip('/').split('/')[-1]
price_text = price_tag.get_text()
price = int(''.join(ch for ch in price_text if ch.isdigit()))
title = title_tag.get_text(strip=True) if title_tag else None
address = ', '.join(g.get_text(strip=True) for g in geo_tags)
result.append({'offer_id': offer_id, 'url': url, 'title': title, 'price': price, 'address': address})
return result- Bauen Sie die Hauptschleife. Sie durchläuft die Seiten, macht eine zufällige Pause zwischen den Anfragen und sammelt die Ergebnisse in einer Gesamtliste. Die zufällige Pause ist wichtig: Gleiche Intervalle wirken unnatürlich und erzeugen Spitzenlast:
def collect_listing():
session = requests.Session()
all_offers = []
for page in range(1, MAX_PAGES + 1):
html = fetch_page(page, session)
if html is None:
break
offers = parse_cards(html)
print('Seite', page, 'Objekte:', len(offers))
if not offers:
break
all_offers.extend(offers)
time.sleep(random.uniform(PAUSE_MIN, PAUSE_MAX))
return all_offers
if __name__ == '__main__':
data = collect_listing()
for item in data[:5]:
print(item)
print('Insgesamt gesammelt:', len(data))- Speichern Sie die Datei und starten Sie sie im Terminal mit
python parser.py. Stellen Sie sicher, dass die venv-Umgebung aktiv ist.
Erläutern wir die Schlüsselstellen. Session speichert Cookies zwischen Anfragen, sodass die Website das konsequente Verhalten eines Besuchers sieht und nicht ein Dutzend zusammenhangloser Aufrufe. Die Funktion select_one gibt das erste passende Element oder None zurück, deshalb prüfen wir immer das Ergebnis, bevor wir get_text aufrufen. Die Anzeigen-ID entnehmen wir dem Link: Das ist der letzte Teil der Adresse, eine Zahl wie 312456789. Genau sie wird der Schlüssel für den Preisverlauf.
Tipp: Speichern Sie beim Debuggen das HTML der ersten Seite in eine Datei mit dem Befehl open('page1.html', 'w', encoding='utf-8').write(html). Dann können Sie die Parsing-Funktion an einer lokalen Kopie testen, ohne zusätzliche Anfragen an die Website zu senden.
Prüfung: In der Konsole sehen Sie Zeilen wie Seite 1 Objekte: 28, Seite 2 Objekte: 28 und so weiter, und unten fünf Wörterbücher mit echten Preisen und Adressen. Preise sollten ganze Zahlen ohne Leerzeichen und Rubelzeichen sein. Wenn statt Zahlen nichts erscheint, kehren Sie zu den Selektoren aus Schritt 2 zurück.
Mögliche Probleme
Das Skript gibt Objekte: 0 auf der ersten Seite aus. Ursachen: Der Karten-Selektor hat sich geändert oder der Server hat eine Platzhalterseite zurückgegeben. Öffnen Sie die gespeicherte page1.html im Browser und schauen Sie, was Sie erhalten haben. Wenn es eine Seite mit der Aufforderung ist, zu bestätigen, dass Sie kein Roboter sind, erhöhen Sie die Pausen und gehen Sie zu Schritt 5 mit Proxys über. Wenn es eine normale Ergebnisseite ist, vergleichen Sie die Selektoren.
Fehler ValueError bei der Preisumwandlung. Ursache: Im Preistext gibt es keine Ziffern, zum Beispiel steht dort „Preis auf Anfrage“. Lösung: Umwickeln Sie die Umwandlung mit try und schreiben Sie None für solche Fälle.
Schritt 4: Objekt-Detailseiten sammeln
Ziel dieses Schritts: Den Scraper so erweitern, dass er die Seite jedes Objekts aufruft und detaillierte Merkmale extrahiert: Fläche, Etage, Haustyp, Baujahr. Diese Felder werden für die Berechnung des Quadratmeterpreises und den Vergleich ähnlicher Wohnungen benötigt.
- Öffnen Sie im Browser die Seite eines beliebigen Objekts aus den Ergebnissen. Drücken Sie Strg+U, um den Quellcode der Seite anzuzeigen.
- Drücken Sie Strg+F und geben Sie das Wort totalArea ein. Zum Zeitpunkt dieser Anleitung liegen die Kartendaten im script-Tag innerhalb des Frontend-Konfigurationsobjekts, in einem Schlüssel mit einem Namen wie frontend-offer-card. Sie sehen die Felder totalArea, floorNumber, floorsCount, buildYear, materialType und andere.
- Wenn die Suche nach totalArea nichts ergibt, suchen Sie die Merkmale im HTML: Normalerweise ist das ein Block mit Paaren aus Name und Wert, zum Beispiel „Gesamtfläche“ und „38,5 m²“. Notieren Sie den Selektor dieses Blocks.
- Fügen Sie in parser.py eine Funktion zum Extrahieren von JSON aus der Karte hinzu. Wir finden das benötigte script, schneiden das Objekt an den geschweiften Klammern aus und parsen es mit dem json-Modul:
import json
import re
def parse_offer_page(html):
soup = BeautifulSoup(html, 'lxml')
details = {}
for script in soup.find_all('script'):
text = script.string or ''
if 'totalArea' in text and 'offerData' in text:
m = re.search(r'totalArea[^0-9]*([0-9.,]+)', text)
if m:
details['area'] = float(m.group(1).replace(',', '.'))
m = re.search(r'floorNumber[^0-9]*([0-9]+)', text)
if m:
details['floor'] = int(m.group(1))
m = re.search(r'floorsCount[^0-9]*([0-9]+)', text)
if m:
details['floors_total'] = int(m.group(1))
m = re.search(r'buildYear[^0-9]*([0-9]{4})', text)
if m:
details['build_year'] = int(m.group(1))
break
return details- Hier verwenden wir bewusst reguläre Ausdrücke statt vollständiger JSON-Analyse. Der Grund ist einfach: Das Skript auf der Seite enthält nicht nur JSON, sondern auch Code, und ein reines Objekt herauszulösen ist manchmal schwierig. Reguläre Ausdrücke suchen den Schlüssel und die erste Zahl danach, was für numerische Felder ausreichend zuverlässig ist.
- Fügen Sie eine Funktion hinzu, die die Liste der Objekte aus den Ergebnissen nimmt und jedes mit den Kartendaten anreichert. Pausen sind hier noch wichtiger, weil die Anzahl der Anfragen um das 28-fache steigt:
def enrich_offers(offers, session):
for i, offer in enumerate(offers, 1):
try:
resp = session.get(offer['url'], headers=HEADERS, timeout=30)
if resp.status_code == 200:
offer.update(parse_offer_page(resp.text))
else:
print('Karte', offer['offer_id'], 'Status', resp.status_code)
except requests.RequestException as e:
print('Netzwerkfehler bei', offer['offer_id'], e)
if i % 10 == 0:
print('Bearbeitete Karten:', i)
time.sleep(random.uniform(PAUSE_MIN, PAUSE_MAX))
return offers- Fügen Sie im Block if __name__ nach collect_listing den Aufruf enrich_offers(data, requests.Session()) hinzu und starten Sie das Skript erneut mit MAX_PAGES gleich 1, um nicht lange zu warten.
Wie lange das dauert: 28 Karten bei einer durchschnittlichen Pause von 6 Sekunden sind etwa 3 Minuten. Die vollständige Sammlung von 5 Ergebnisseiten mit Karten dauert etwa 15 Minuten. Das ist normal. Ein Immobilien-Scraper muss nicht schnell sein, er muss stabil sein.
Tipp: Parsen Sie die Karten nicht bei jedem Lauf neu. Die Merkmale einer Wohnung ändern sich nicht: Fläche und Baujahr reichen einmal. Nur der Preis ändert sich, und der steht in den Ergebnissen. In Schritt 6 sorgen wir dafür, dass die Karte nur für neue Objekte abgefragt wird. Das reduziert die Anzahl der Anfragen um ein Vielfaches.
Prüfung: In der Ausgabe der Wörterbücher erscheinen die Schlüssel area, floor, floors_total und build_year mit plausiblen Werten: Fläche zwischen 15 und 200, Etage nicht größer als die Etagenzahl, Jahr zwischen 1900 und 2026. Wenn bei einigen Objekten Felder fehlen, ist das normal: Nicht alle Verkäufer geben das Baujahr an.
Mögliche Probleme
Der reguläre Ausdruck findet die Zimmerfläche statt der Gesamtfläche. Ursache: Im JSON gibt es ähnliche Schlüssel wie livingArea oder kitchenArea. Lösung: Präzisieren Sie das Muster, indem Sie vor dem Schlüssel ein Anführungszeichen oder einen Doppelpunkt hinzufügen, damit es nicht mit einem Teil eines anderen Wortes übereinstimmt.
Schritt 5: Mobile Proxys einbinden und die Sammlung stabil machen
Ziel dieses Schritts: Anfragen über mobile Proxys mit IP-Rotation verteilen, Wiederholungsversuche und korrekte Antwortverarbeitung hinzufügen. Nach diesem Schritt kann der Scraper regelmäßig und lange arbeiten, ohne übermäßige Last von einer Adresse zu erzeugen.
Warum ein Immobilien-Scraper mobile Proxys braucht
Jede große Plattform begrenzt die Anfragefrequenz von einer IP-Adresse. Das ist ein Schutz vor Überlastung und greift bei jeder Automatisierung. Eine Heimadresse beginnt nach einigen hundert Anfragen, Antworten mit Status 429 oder Seiten mit Überprüfung zu erhalten. Mobile Proxys lösen die Aufgabe anders: Sie erhalten eine IP aus einem Mobilfunkpool, und per Befehl oder Timer ändert sich die Adresse. Ihre Anfragen verteilen sich auf mehrere Adressen, die Last auf jeder einzelnen bleibt niedrig, und der Scraper läuft gleichmäßig. Für regelmäßige Preisüberwachung ist das entscheidend: Sie brauchen keine einmaligen Daten, sondern tägliche Momentaufnahmen über Monate.
Einrichtung
- Öffnen Sie den Kundenbereich Ihres mobilen Proxy-Anbieters und finden Sie den gekauften Proxy. Kopieren Sie vier Werte: Host, Port, Benutzername, Passwort. Kopieren Sie auch den Link zum IP-Wechsel: Normalerweise sieht er wie eine Adresse mit einem Schlüssel aus, und beim Aufruf erhält der Proxy eine neue Adresse.
- Fügen Sie die Proxy-Parameter in config.py hinzu. Schreiben Sie Passwörter niemals in Code, den Sie irgendwo veröffentlichen: Halten Sie sie in einer separaten Datei oder Umgebungsvariablen:
PROXY_HOST = 'Ihr_Host'
PROXY_PORT = 'Ihr_Port'
PROXY_USER = 'Ihr_Login'
PROXY_PASS = 'Ihr_Passwort'
ROTATE_URL = 'Link_zum_IP_Wechsel'
ROTATE_EVERY = 25- Fügen Sie in parser.py eine Funktion zum Erstellen einer Session mit Proxy hinzu. Die requests-Bibliothek akzeptiert ein Wörterbuch mit Adressen für http und https:
from config import PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS, ROTATE_URL, ROTATE_EVERY
def make_session():
session = requests.Session()
proxy_url = 'http://' + PROXY_USER + ':' + PROXY_PASS + '@' + PROXY_HOST + ':' + PROXY_PORT
session.proxies = {'http': proxy_url, 'https': proxy_url}
return session
def rotate_ip():
try:
r = requests.get(ROTATE_URL, timeout=20)
print('IP-Wechsel:', r.status_code)
time.sleep(5)
except requests.RequestException as e:
print('IP-Wechsel fehlgeschlagen:', e)- Prüfen Sie, ob der Proxy funktioniert. Erstellen Sie eine temporäre Datei check_proxy.py mit Code, der über die Session einen IP-Bestimmungsdienst abfragt und die Antwort ausgibt:
from parser import make_session
s = make_session()
print(s.get('https://api.ipify.org', timeout=20).text)- Führen Sie es aus. Sie sollten eine IP-Adresse sehen, die sich von Ihrer Heimadresse unterscheidet. Rufen Sie rotate_ip auf und führen Sie die Prüfung erneut aus: Die Adresse sollte sich geändert haben.
- Fügen Sie nun eine Funktion für Anfragen mit Wiederholungsversuchen hinzu. Sie behandelt drei Situationen: erfolgreiche Antwort, Antwort 429 oder 403 (warten und Adresse wechseln), Netzwerkfehler (wiederholen):
def safe_get(session, url, params=None, retries=3):
for attempt in range(1, retries + 1):
try:
resp = session.get(url, params=params, headers=HEADERS, timeout=30)
if resp.status_code == 200:
return resp
if resp.status_code in (429, 403):
print('Status', resp.status_code, 'Versuch', attempt, 'IP wechseln und warten')
rotate_ip()
time.sleep(30 * attempt)
continue
print('Unerwarteter Status', resp.status_code)
return None
except requests.RequestException as e:
print('Netzwerkfehler', e, 'Versuch', attempt)
time.sleep(10 * attempt)
return None- Ersetzen Sie die Aufrufe von session.get in fetch_page und enrich_offers durch safe_get. Fügen Sie in enrich_offers einen Zähler hinzu: Alle ROTATE_EVERY Anfragen rufen Sie rotate_ip auf. Das ist eine geplante Rotation, die verhindert, dass eine Adresse zu viele Anfragen ansammelt.
Achtung: Wenn Sie eine Antwort 429 oder eine Seite mit Überprüfung erhalten, versuchen Sie nicht, sie mit häufigen Wiederholungen zu durchbrechen. Das verschlechtert die Situation für die aktuelle Adresse nur. Die richtige Reaktion: anhalten, Pausen erhöhen, IP wechseln und in ruhigem Tempo fortfahren. Ein Scraper, der die Limits der Website respektiert, lebt länger und sammelt mehr.
Tipp: Verwenden Sie einen Proxy-Kanal pro Scraping-Thread. Die Versuchung, zehn Threads über eine Adresse laufen zu lassen, ist groß, aber das ist ein direkter Weg zu Einschränkungen. Wenn Sie Geschwindigkeit brauchen, kaufen Sie mehrere Kanäle und verteilen Sie verschiedene Regionen oder Filter darauf.
Prüfung: check_proxy.py zeigt eine Mobilfunkadresse, nach der Rotation ändert sich die Adresse. Der Scraper durchläuft zwei Ergebnisseiten mit Karten ohne einen einzigen Status 429. Im Log sind Zeilen wie IP-Wechsel: 200 alle 25 Karten sichtbar.
Mögliche Probleme
Fehler ProxyError oder 407. Ursache: Falscher Benutzername oder Passwort oder falscher Port. Lösung: Prüfen Sie die Daten im Kundenbereich, stellen Sie sicher, dass Sie den Port für HTTP-Proxy verwenden, nicht SOCKS. Wenn Sie SOCKS5 haben, installieren Sie die Bibliothek pysocks und verwenden Sie das Präfix socks5h statt http in proxy_url.
Nach der Rotation ändert sich die Adresse nicht. Ursache: Der Betreiber hat dieselbe Adresse vergeben oder die Rotation ist noch nicht angewendet. Lösung: Erhöhen Sie die Pause nach rotate_ip auf 10 Sekunden und prüfen Sie, ob die Häufigkeit des IP-Wechsels in Ihrem Tarif begrenzt ist.
Schritt 6: Daten speichern und Preisverlauf aufbauen
Ziel dieses Schritts: Den Scraper von der Konsolenausgabe auf das Schreiben in eine SQLite-Datenbank umstellen, sodass jeder Lauf einen neuen Punkt im Preisverlauf hinzufügt und nicht den alten überschreibt. Das ist das Herz des gesamten Projekts.
Tabellen entwerfen
Wir brauchen zwei Tabellen. Die erste, offers, speichert das Objekt: eine Zeile pro Anzeige mit Merkmalen und Daten. Die zweite, prices, speichert den Preis pro Datum: mehrere Zeilen pro Anzeige. Die Trennung ist nötig, um Fläche und Adresse nicht bei jeder Preisaufzeichnung zu duplizieren.
- Erstellen Sie die Datei storage.py und beschreiben Sie die Tabellenerstellung:
import sqlite3
from datetime import date
from config import DB_PATH
def get_conn():
conn = sqlite3.connect(DB_PATH)
conn.execute('CREATE TABLE IF NOT EXISTS offers (offer_id TEXT PRIMARY KEY, url TEXT, title TEXT, address TEXT, area REAL, floor INTEGER, floors_total INTEGER, build_year INTEGER, first_seen TEXT, last_seen TEXT, is_active INTEGER DEFAULT 1)')
conn.execute('CREATE TABLE IF NOT EXISTS prices (offer_id TEXT, checked_on TEXT, price INTEGER, PRIMARY KEY (offer_id, checked_on))')
conn.commit()
return conn- Fügen Sie eine Funktion hinzu, die die Menge der bereits bekannten offer_id zurückgibt. Sie wird benötigt, um Karten nur für neue Objekte abzufragen:
def known_ids(conn):
rows = conn.execute('SELECT offer_id FROM offers').fetchall()
return set(r[0] for r in rows)- Schreiben Sie eine Speicherfunktion. Für ein neues Objekt fügen wir eine Zeile in offers ein. Für jedes Objekt aktualisieren wir last_seen und zeichnen den Preis für heute auf. Die Konstruktion INSERT OR REPLACE in prices bedeutet: Wenn der Preis heute schon aufgezeichnet wurde, aktualisieren, sonst hinzufügen:
def save_offers(conn, offers):
today = date.today().isoformat()
for o in offers:
conn.execute('INSERT OR IGNORE INTO offers (offer_id, url, title, address, area, floor, floors_total, build_year, first_seen, last_seen) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', (o['offer_id'], o['url'], o.get('title'), o.get('address'), o.get('area'), o.get('floor'), o.get('floors_total'), o.get('build_year'), today, today))
conn.execute('UPDATE offers SET last_seen = ?, is_active = 1 WHERE offer_id = ?', (today, o['offer_id']))
if o.get('price'):
conn.execute('INSERT OR REPLACE INTO prices (offer_id, checked_on, price) VALUES (?, ?, ?)', (o['offer_id'], today, o['price']))
conn.commit()- Fügen Sie eine Funktion hinzu, die entfernte Objekte markiert. Wenn eine Anzeige mehr als drei Tage nicht in den Ergebnissen erscheint, betrachten wir sie als inaktiv. Das gibt Ihnen Daten über die Verkaufsgeschwindigkeit:
def mark_inactive(conn, days=3):
conn.execute('UPDATE offers SET is_active = 0 WHERE julianday(?) - julianday(last_seen) > ?', (date.today().isoformat(), days))
conn.commit()- Schreiben Sie den Hauptblock von parser.py so um, dass er die Ergebnisse sammelt, neue Objekte bestimmt, nur diese anreichert und alles speichert:
from storage import get_conn, known_ids, save_offers, mark_inactive
if __name__ == '__main__':
conn = get_conn()
session = make_session()
listing = collect_listing(session)
old = known_ids(conn)
new_offers = [o for o in listing if o['offer_id'] not in old]
print('Neue Objekte:', len(new_offers), 'von', len(listing))
enrich_offers(new_offers, session)
save_offers(conn, listing)
mark_inactive(conn)
print('Gespeichert. Insgesamt in der Datenbank:', conn.execute('SELECT COUNT(*) FROM offers').fetchone()[0])- Vergessen Sie nicht, collect_listing so zu ändern, dass sie session als Parameter akzeptiert und keine eigene erstellt. Starten Sie das Skript. Im Projektordner erscheint die Datei realty.db.
Preisverlauf ansehen
Erstellen Sie die Datei report.py, die Preisänderungen in Excel exportiert. Die folgende Abfrage findet Objekte, bei denen der letzte Preis vom ersten abweicht:
import sqlite3
import pandas as pd
from config import DB_PATH
conn = sqlite3.connect(DB_PATH)
query = 'SELECT o.offer_id, o.address, o.area, o.url, MIN(p.checked_on) AS first_date, MAX(p.checked_on) AS last_date, (SELECT price FROM prices WHERE offer_id = o.offer_id ORDER BY checked_on ASC LIMIT 1) AS first_price, (SELECT price FROM prices WHERE offer_id = o.offer_id ORDER BY checked_on DESC LIMIT 1) AS last_price FROM offers o JOIN prices p ON p.offer_id = o.offer_id GROUP BY o.offer_id'
df = pd.read_sql(query, conn)
df['change_pct'] = ((df['last_price'] - df['first_price']) / df['first_price'] * 100).round(1)
df['price_per_m2'] = (df['last_price'] / df['area']).round(0)
df = df.sort_values('change_pct')
df.to_excel('report.xlsx', index=False)
print(df.head(10))Nach dem ersten Lauf ist die Spalte change_pct null: Es gibt noch keine Historie. Ab dem zweiten Tag erscheinen die ersten Änderungen. Nach zwei Wochen sehen Sie das Bild: Wie viele Objekte haben den Preis gesenkt, um wie viel im Durchschnitt, welche Viertel bewegen sich schneller.
Tipp: Fügen Sie am Ende von parser.py das Kopieren der Datenbank hinzu: shutil.copy(DB_PATH, 'backup_' + date.today().isoformat() + '.db'). Eine Codezeile schützt Wochen gesammelter Daten. Löschen Sie einmal im Monat alte Kopien und behalten Sie die letzten fünf.
Prüfung: Die Datei realty.db existiert, report.xlsx öffnet sich in Excel, enthält Spalten mit Adresse, Fläche, Preisen und Quadratmeterpreis. Starten Sie parser.py ein zweites Mal nach einigen Minuten: Die Zeile Neue Objekte sollte 0 oder eine kleine Zahl zeigen, und Karten sollten nicht erneut abgefragt werden. Das bestätigt, dass die Deduplizierung funktioniert.
Mögliche Probleme
Fehler database is locked. Ursache: Die Datenbank ist in einem anderen Programm geöffnet, zum Beispiel in einem SQLite-Viewer, oder zwei Skriptinstanzen laufen gleichzeitig. Lösung: Schließen Sie überflüssige Programme und starten Sie das Skript nicht parallel zu sich selbst.
In report.xlsx zeigen alle Objekte dasselbe Datum. Ursache: Das Skript lief nur an einem Tag. Das ist zu erwarten, warten Sie einfach auf die nächsten Läufe.
Schritt 7: Automatischen Start einrichten und auf andere Plattformen erweitern
Ziel dieses Schritts: Dafür sorgen, dass der Scraper jeden Morgen automatisch läuft, und den Code vorbereiten, um andere Immobilienplattformen anzubinden. Der Preisverlauf ist nur bei Regelmäßigkeit wertvoll, daher ist Automatisierung Pflicht.
Zeitplan
- Öffnen Sie unter Windows die Aufgabenplanung über die Suche im Startmenü. Klicken Sie auf Einfache Aufgabe erstellen. Geben Sie einen Namen ein, zum Beispiel Immobilien-Scraper.
- Wählen Sie den Trigger Täglich, stellen Sie die Uhrzeit ein, zum Beispiel 07:30. Der frühe Morgen ist günstig: Die Last auf den Websites ist minimal, und zum Arbeitsbeginn haben Sie frische Daten.
- Wählen Sie als Aktion Programm starten. Im Feld Programm geben Sie den vollständigen Pfad zu python.exe im venv-Ordner an, zum Beispiel C:\projects\cian_parser\venv\Scripts\python.exe. Im Feld Argumente geben Sie parser.py ein. Im Feld Arbeitsverzeichnis geben Sie den Projektordner an.
- Speichern Sie die Aufgabe und klicken Sie rechts auf Ausführen, um zu testen. Im Projektordner sollte sich die Datenbank aktualisieren.
- Verwenden Sie unter macOS und Linux cron. Geben Sie im Terminal crontab -e ein und fügen Sie die Zeile hinzu:
30 7 * * * cd /Pfad/zu/cian_parser && ./venv/bin/python parser.py >> run.log 2>&1. Das Log aller Läufe sammelt sich in run.log.
Vorbereitung auf andere Plattformen
Domclick, Yandex Nedvizhimost, Metr Kvadratny und regionale Portale sind ähnlich aufgebaut, aber ihre Selektoren und Filterparameter sind anders. Um den Scraper nicht für jede Plattform neu zu schreiben, lagern Sie die Unterschiede in separate Module aus.
- Erstellen Sie im Projekt den Ordner sites. Darin erstellen Sie die Datei cian.py und verschieben die Funktionen fetch_page und parse_cards zusammen mit den Suchparametern dorthin. Behalten Sie eine einheitliche Schnittstelle bei: Die Funktion parse_cards nimmt HTML und gibt eine Liste von Wörterbüchern mit denselben Schlüsseln offer_id, url, title, price, address zurück.
- Für eine neue Plattform erstellen Sie eine Datei, zum Beispiel domclick.py, und wiederholen die Untersuchung aus Schritt 2: Öffnen Sie die Ergebnisseite, finden Sie Karte, Preis, Link und Paginierungsparameter. Schreiben Sie Ihre eigenen Versionen von fetch_page und parse_cards.
- Fügen Sie in offer_id ein Plattformpräfix hinzu, zum Beispiel cian_312456789 und domclick_98765. Sonst können IDs von verschiedenen Websites übereinstimmen und den Verlauf vermischen.
- Importieren Sie in parser.py die Module aus sites und starten Sie die Sammlung für jedes in einer Schleife. Die Tabellen in der Datenbank sind gemeinsam: ein Schema für alle Quellen, und die Spalte source verrät, woher das Objekt kommt.
Wichtiger Hinweis zu Domclick und Yandex Nedvizhimost: Diese Plattformen nutzen aktiv interne APIs im JSON-Format, die im Tab Network sichtbar sind. Ihre Ergebnisse sind oft bequemer zu parsen als HTML, aber die Antwortstruktur ändert sich häufiger. Prüfen Sie Selektoren und Felder einmal im Monat.
Tipp: Für dasselbe Objekt, das auf mehreren Plattformen angeboten wird, können die Preise unterschiedlich sein. Der Vergleich solcher Paare liefert interessante Analysen und hilft, Verkäufer zu finden, die irgendwo den Preis gesenkt, aber vergessen haben, ihn anderswo zu aktualisieren. Objekte lassen sich anhand von Adresse, Fläche und Etage zuordnen.
Prüfung: Die Aufgabe im Planer wurde manuell ohne Fehler ausgeführt, in run.log oder im Planer-Verlauf ist eine erfolgreiche Markierung sichtbar. Die Ordnerstruktur enthält sites mit mindestens einem Modul, der Scraper startet aus dem Projektstamm und funktioniert wie zuvor.
Mögliche Probleme
Der Planer meldet, die Aufgabe sei ausgeführt, aber die Datenbank wurde nicht aktualisiert. Ursache: Das Skript startete aus einem anderen Arbeitsverzeichnis und erstellte irgendwo eine neue leere Datenbank. Lösung: Füllen Sie das Feld Arbeitsverzeichnis in der Aufgabe aus oder verwenden Sie einen absoluten Pfad zur Datenbank in config.py.
Ergebnisprüfung: Checkliste für einen fertigen Scraper
Gehen Sie die Liste durch und haken Sie jeden Punkt ab. Wenn alles erledigt ist, haben Sie einen vollwertigen Immobilien-Scraper mit Preisverlauf.
- Das Skript parser.py startet mit einem Befehl aus der aktivierten Umgebung ohne Importfehler.
- Die Ergebnisse werden von mehreren Seiten gesammelt, die Anzahl der Objekte pro Seite entspricht dem, was Sie im Browser sehen.
- Preise werden als ganze Zahlen gespeichert, Adressen sind lesbar, Links öffnen sich.
- Karten werden nur für neue Objekte abgefragt, im Log ist die Zeile Neue Objekte mit sinkender Zahl bei wiederholten Läufen sichtbar.
- Anfragen laufen über einen mobilen Proxy, check_proxy.py zeigt die Betreiberadresse, die Rotation ändert sie.
- Bei Status 429 macht der Scraper eine Pause und wechselt die IP, anstatt abzustürzen.
- Die Datei realty.db wächst, die Tabelle prices erhält jeden Tag neue Zeilen.
- report.xlsx wird erstellt und öffnet sich, nach einigen Tagen erscheinen darin von Null verschiedene Preisänderungen.
- Der Autostart ist eingerichtet, das Log erfasst jeden Durchlauf.
- Eine Sicherungskopie der Datenbank wird automatisch erstellt.
Wie man alles komplett testet
- Löschen oder benennen Sie realty.db um, um sauber zu beginnen.
- Setzen Sie MAX_PAGES auf 2 und starten Sie parser.py. Messen Sie die Zeit: Es sollten etwa 6-8 Minuten mit Karten sein.
- Starten Sie report.py und prüfen Sie, dass der Bericht etwa 56 Zeilen enthält.
- Öffnen Sie realty.db mit einem beliebigen SQLite-Viewer oder führen Sie in Python die Abfrage SELECT COUNT(*) FROM prices aus. Die Zahl sollte mit der Anzahl der Objekte übereinstimmen.
- Starten Sie parser.py erneut. Die Laufzeit sollte auf eine Minute sinken, weil Karten nicht abgefragt werden. Die Anzahl der Zeilen in prices ändert sich nicht, da das Datum dasselbe ist.
- Ändern Sie in der Datenbank den Preis eines Objekts manuell auf eine andere Zahl, ändern Sie das Aufzeichnungsdatum auf gestern und starten Sie den Scraper. In report.xlsx sollte dieses Objekt eine Preisänderung anzeigen. So überzeugen Sie sich, dass die Verlaufslogik funktioniert, ohne auf echte Änderungen zu warten.
Erfolgsindikatoren
Der Anteil der Objekte mit ausgefüllter Fläche liegt über 90 Prozent. Der Anteil der Anfragen mit Status 200 liegt über 97 Prozent. Keine unbehandelte Ausnahme während eines Durchlaufs. Die Zeit eines vollständigen Durchlaufs ist vorhersehbar und wächst nicht von Lauf zu Lauf. Wenn die Werte niedriger sind, kehren Sie zum Abschnitt mit den typischen Fehlern zurück.
Typische Fehler und Lösungen
Wir haben Probleme gesammelt, mit denen fast alle konfrontiert werden, die zum ersten Mal einen Immobilien-Anzeigen-Scraper schreiben. Format: Problem, Ursache, Lösung.
Der Scraper gibt 0 Objekte zurück, obwohl er gestern funktionierte
Ursache: Die Plattform hat das Layout aktualisiert und die Attribute data-mark oder data-name geändert. Lösung: Öffnen Sie die Ergebnisseite im Browser, wiederholen Sie Schritt 2 und aktualisieren Sie die Selektoren. Gewöhnen Sie sich an, Selektoren an einer Stelle am Anfang des Moduls zu speichern, damit Änderungen eine Minute dauern. Fügen Sie eine Prüfung hinzu: Wenn auf der ersten Seite 0 Objekte sind, senden Sie sich eine Benachrichtigung per Messenger.
Preise werden um den Faktor tausend falsch gespeichert
Ursache: Bei einigen Objekten ist der Preis in Tausend oder mit dem Zusatz „pro Monat“ angegeben, oder im Text ist der Quadratmeterpreis gelandet. Lösung: Stellen Sie sicher, dass Sie genau MainPrice nehmen und nicht das benachbarte Element mit dem Preis pro Meter. Fügen Sie eine Plausibilitätsprüfung hinzu: Ein Verkaufspreis einer Wohnung in Moskau unter einer Million Rubel ist fast sicher ein Parsingfehler, protokollieren Sie solche Fälle.
Status 429 kommt bereits auf der dritten Seite
Ursache: Die Pausen sind zu kurz oder der Proxy ist noch nicht eingebunden, alle Anfragen gehen von einer Heim-IP. Lösung: Erhöhen Sie PAUSE_MIN und PAUSE_MAX auf 6 und 12, binden Sie einen mobilen Proxy ein, aktivieren Sie die geplante Rotation alle 20-25 Anfragen. Prüfen Sie, dass Sie nicht mehrere Skriptinstanzen gleichzeitig gestartet haben.
Fehler UnicodeEncodeError bei der Ausgabe in der Windows-Konsole
Ursache: Die Standard-Windows-Konsole gibt kyrillische Zeichen nicht immer korrekt aus. Lösung: Führen Sie im Terminal chcp 65001 vor dem Start aus oder fügen Sie am Anfang des Skripts die Zeile sys.stdout.reconfigure(encoding='utf-8') hinzu. Sie können Logs auch in eine Datei statt in die Konsole schreiben.
Die Adresse wird unvollständig oder mit Duplikaten gesammelt
Ursache: Die Adresse auf der Karte besteht aus mehreren GeoLabel-Links, einige duplizieren Stadt und Bezirk. Lösung: Entfernen Sie Duplikate unter Beibehaltung der Reihenfolge oder nehmen Sie die Adresse aus der Objektkarte, wo sie als einzelne Zeichenkette dargestellt wird. Für die Analyse nach Vierteln fügen Sie ein separates Feld district hinzu und schneiden es anhand einer bekannten Liste von Vierteln aus der Adresse heraus.
Der Scraper läuft manuell, aber nicht im Planer
Ursache: Der Planer verwendet einen anderen Python-Interpreter ohne installierte Bibliotheken oder ein anderes Arbeitsverzeichnis. Lösung: Geben Sie den absoluten Pfad zu python.exe im venv an und füllen Sie das Feld Arbeitsverzeichnis aus. Leiten Sie die Ausgabe in eine Logdatei um, um Fehler zu sehen.
Die Datenbank ist nach einem Monat mehrere Gigabyte groß
Ursache: Sie speichern das vollständige HTML der Seiten oder alle JSON-Felder in der Datenbank. Lösung: Speichern Sie nur die benötigten Felder. Wenn Sie die Originalseiten für erneutes Parsen behalten wollen, legen Sie sie in komprimierten Dateien auf der Festplatte ab, nicht in SQLite. Führen Sie einmal im Quartal den Befehl VACUUM zur Komprimierung der Datenbank aus.
Gleiche Objekte werden unter verschiedenen IDs dupliziert
Ursache: Der Verkäufer hat die Anzeige entfernt und neu eingestellt, wodurch eine neue Nummer entstand. Lösung: Fügen Sie einen zusätzlichen Zuordnungsschlüssel aus Adresse, Fläche und Etage hinzu. Objekte mit demselben Schlüssel, aber unterschiedlichen offer_id können in einer separaten Tabelle verknüpft und der Preisverlauf über die Verknüpfung berechnet werden. Das ist bereits fortgeschrittene Analytik, aber genau sie zeigt echte Preissenkungen, die hinter einer Neueinstellung verborgen sind.
Zusätzliche Möglichkeiten für Fortgeschrittene
Der Basis-Scraper ist fertig. Wenn Sie mehr wollen, hier sind die Richtungen mit dem größten Nutzen.
Browser-Automatisierung mit Playwright
Einige Seiten laden Daten erst nach dem Laden per Skript, und requests erhält ein leeres Gerüst. In solchen Fällen verwenden Sie Playwright: Es steuert einen echten Browser. Installieren Sie es mit den Befehlen pip install playwright und playwright install chromium. Der Proxy wird beim Start des Browsers im Parameter proxy mit einem Wörterbuch server, username, password übergeben. Warten Sie auf das Erscheinen der Karten mit page.wait_for_selector und übergeben Sie page.content() an die bereits geschriebene Funktion parse_cards. Beachten Sie, dass der Browser zehnmal mehr Ressourcen verbraucht, verwenden Sie ihn also gezielt nur für problematische Seiten.
Parallele Sammlung über mehrere Proxy-Kanäle
Wenn Sie mehrere Regionen sammeln müssen, kaufen Sie einen separaten mobilen Proxy für jede Region und starten Sie einen separaten Prozess pro Kanal. Verwenden Sie keine Multithreading innerhalb eines Kanals: Der Sinn der Lastverteilung geht verloren. Einfache Methode: Der Regionsparameter wird dem Skript als Kommandozeilenargument übergeben, und der Planer startet mehrere Aufgaben mit unterschiedlichen Argumenten und einem kleinen Zeitversatz.
Benachrichtigungen über Preissenkungen
Fügen Sie am Ende des Scrapers einen Vergleich des heutigen Preises mit dem vorherigen für jedes Objekt hinzu. Wenn die Senkung größer als ein festgelegter Schwellenwert ist, zum Beispiel 3 Prozent, erstellen Sie eine Nachricht mit Adresse, altem und neuem Preis, Link und senden Sie sie sich über einen Bot im Messenger. Das verwandelt den Scraper von einem Analysewerkzeug in ein Handlungswerkzeug: Sie erfahren innerhalb einer Stunde nach der Änderung von günstigen Objekten.
Analyse und Visualisierung
Mit pandas können Sie Daten nach Vierteln gruppieren und den medianen Quadratmeterpreis, den Anteil der Objekte mit Preissenkung, die durchschnittliche Expositionszeit (Differenz zwischen first_seen und last_seen für inaktive Objekte) berechnen. Die Bibliothek matplotlib erstellt in drei Codezeilen ein Diagramm der Dynamik über einen Monat. Laden Sie den Bericht in Google Sheets hoch, und Kollegen ohne Programmierkenntnisse erhalten ein lebendiges Dashboard.
Speicherung in PostgreSQL
Wenn es mehr als hunderttausend Objekte gibt, beginnt SQLite bei analytischen Abfragen zu bremsen. Der Umzug auf PostgreSQL ist einfach: Das Tabellenschema bleibt gleich, nur die Verbindungszeichenfolge und die Bibliothek ändern sich (psycopg2 statt sqlite3). Tun Sie das nur bei echtem Bedarf: Für eine Stadt reicht SQLite jahrelang.
Gesundheitsüberwachung des Scrapers
Schreiben Sie in eine separate Tabelle runs die Startzeit, Endzeit, Anzahl der gesammelten Objekte, Anzahl der Fehler und Anzahl der IP-Rotationen. Wenn die Objekte plötzlich weniger werden oder die Fehler über 5 Prozent liegen, senden Sie eine Benachrichtigung. Eine solche Überwachung ermöglicht es, eine Layout-Änderung am Tag ihres Erscheinens zu bemerken und nicht nach zwei Wochen anhand eines leeren Berichts.
FAQ: Häufige Fragen zur Erstellung eines Immobilien-Scrapers
Ist es legal, Immobilienportale zu scrapen?
Das Sammeln öffentlich zugänglicher Informationen über Objekte für die persönliche Analyse ist im Allgemeinen zulässig, aber die Bedingungen jeder Plattform sind in ihren Nutzungsbedingungen beschrieben, und diese müssen Sie lesen. Es ist streng verboten, personenbezogene Daten von Verkäufern zu sammeln, eine kopierte Datenbank als eigene zu veröffentlichen und eine Last zu erzeugen, die den Dienst beeinträchtigt. Wenn Sie eine kommerzielle Nutzung der Daten planen, konsultieren Sie einen Anwalt.
Warum mobile Proxys und nicht Server-Proxys?
Die Adressen von Mobilfunkbetreibern sind für Tausende echte Abonnenten gemeinsam und ändern sich ständig. Plattformen behandeln sie wohlwollender als Rechenzentrumsadressen, von denen echte Käufer fast nie kommen. Dazu kommt die Möglichkeit, die IP per Link zu wechseln, was eine kontrollierte Rotation ohne den Kauf hunderter Adressen ermöglicht.
Wie oft sollte man den Scraper für den Preisverlauf starten?
Einmal täglich ist optimal. Immobilienpreise ändern sich selten, öfter als einmal pro Tag zu sammeln ist sinnlos, und die Last wächst. Wenn Sie Preissenkungen zeitnah erfassen wollen, starten Sie zweimal täglich morgens und abends, aber nur mit einem engen Filter.
Wie viele Objekte kann man pro Tag über einen Proxy sammeln?
Bei Pausen von 4-9 Sekunden und geplanter Rotation sind etwa 500-700 Anfragen pro Stunde problemlos möglich, oder 8-12 Tausend pro Tag bei rund um die Uhr Betrieb. Für die Überwachung einer Stadt reichen normalerweise 2-3 Tausend Anfragen pro Tag, weil Karten nur für neue Objekte abgefragt werden.
Was tun, wenn sich die Selektoren geändert haben und ich sie nicht finden kann?
Kehren Sie zu Schritt 2 zurück und gehen Sie vom Preis aus: Rechtsklick auf den Preis, Untersuchen, gehen Sie im Baum bis zur Karte nach oben. Suchen Sie Attribute mit dem Wort data und aussagekräftigen Namen: Sie sind stabiler als Klassen mit zufälligen Zeichen. Prüfen Sie auch den Tab Network: Vielleicht kommen die Daten jetzt mit einer separaten JSON-Anfrage, und das Parsen wird sogar einfacher.
Kann man für ein kleines Projekt auf Proxys verzichten?
Für eine einmalige Sammlung von zwei oder drei Seiten ja. Für eine tägliche Überwachung mit Hunderten von Anfragen beginnt eine Heimadresse schnell Einschränkungen zu erhalten, und die Daten werden unvollständig. Ein Preisverlauf mit Lücken verliert an Wert, daher ist für regelmäßige Arbeit ein Proxy nötig.
Wie parst man Miete statt Verkauf?
Ändern Sie den Parameter deal_type auf rent und fügen Sie die Mietart in den Suchparametern hinzu: langfristig oder kurzfristig. Die Links zu den Objekten enthalten rent statt sale, aktualisieren Sie daher den Link-Selektor in parse_cards. Die übrige Logik, einschließlich des Preisverlaufs, funktioniert unverändert.
Muss man Fotos der Objekte speichern?
Für die Preisanalyse nicht. Fotos belegen viel Platz und werden für Berechnungen nicht benötigt. Wenn Sie einen Katalog für den internen Gebrauch erstellen, speichern Sie nur die Links zu den Bildern, nicht die Dateien selbst.
Wie erkennt man, ob ein Objekt verkauft und nicht nur entfernt wurde?
Plattformen nennen den Grund für die Entfernung nicht. Ein indirekter Hinweis: Das Objekt ist aus den Ergebnissen verschwunden und innerhalb eines Monats nicht wieder aufgetaucht. Objekte, die verschwinden und nach einigen Tagen mit einem anderen Preis wieder auftauchen, sind eher neu eingestellt. Verknüpfen Sie sie anhand von Adresse und Fläche, wie im Abschnitt zu Fehlern beschrieben.
Was tun, wenn Daten für zehn Städte benötigt werden?
Legen Sie eine Liste von Regionen in config.py an und starten Sie die Sammlung in einer Schleife mit einem separaten Proxy-Kanal für je zwei bis drei Städte. Verschieben Sie die Starts zeitlich, um nicht alles gleichzeitig zu sammeln. Die Datenbank bleibt gemeinsam, fügen Sie das Feld Region zur Tabelle offers hinzu.
Fazit
Schauen wir, was Sie geschafft haben. Sie haben die Umgebung mit Python und Bibliotheken vorbereitet, verstanden, wie die Ergebnisseite eines Immobilienportals aufgebaut ist, und gelernt, Selektoren selbst zu finden. Sie haben einen Immobilien-Scraper geschrieben, der Ergebnisseiten und Objektkarten sammelt. Sie haben mobile Proxys mit Rotation und Wiederholungsversuchen eingebunden, wodurch die Sammlung stabil und vorhersehbar wurde. Sie haben eine Datenbank mit Preisverlauf entworfen, Berichte und den automatischen Start nach Zeitplan eingerichtet. Das ist ein vollwertiges Arbeitswerkzeug, kein Lehrbeispiel.
Was als Nächstes zu tun ist: Lassen Sie den Scraper zwei Wochen ohne Änderungen laufen. In dieser Zeit sammelt sich Historie, und Sie sehen die Schwachstellen: Wo der Anteil ausgefüllter Felder sinkt, welche Objekte dupliziert werden, wo der Bericht neue Spalten benötigt. Erst danach fügen Sie Funktionen hinzu. Dann binden Sie eine zweite Plattform an, indem Sie die modulare Struktur aus Schritt 7 verwenden: Sie werden überrascht sein, wie viel schneller es beim zweiten Mal geht.
Wohin Sie sich entwickeln können: Benachrichtigungen über Preissenkungen machen das Werkzeug zu einer Quelle von Geschäften. Analysen nach Vierteln und Haustypen machen Sie mit Zahlen in der Hand zum Marktexperten. Die Verknüpfung neu eingestellter Objekte zeigt echte Rabatte, die auf der Website nicht sichtbar sind. Und ein sorgfältiger Umgang mit der Plattform, Pausen, Adressrotation über mobile Proxys und das Sammeln nur der benötigten Daten ermöglichen es dem Werkzeug, monatelang ohne Ausfälle zu arbeiten.
Immobilien-Scraping geht nicht um Geschwindigkeit, sondern um Regelmäßigkeit und Datenqualität. Sie haben das richtige Fundament gelegt. Viel Erfolg beim Sammeln, und möge Ihre Datenbank mit jedem Morgen wachsen.