Mietspiegel erstellen mit immobilienscout24
By Philipp Leppert in R Kartierung Webscraping
June 19, 2017
Angebotsmieten spiegeln die gegenwärtige Mietpreissituation auf dem Wohnungsmarkt wider und können, je nach Region, von den Bestandsmieten deutlich abweichen. Befindet man sich gerade auf der Suche nach einer Wohnung, wird man schnell ein Gefühl dafür bekommen, in welchem Stadtteil Wohnraum teuer oder günstig zu haben ist. Bereits vor der Wohnungssuche kann man sich über die Mietsituation in einer Stadt oder Region mit öffentlich zugänglichen Informationen wie dem Immobilienmarktbericht auseinandersetzen, welcher vom Bundesinstitut für Bau-, Stadt- und Raumforschung erstellt wird. Auch Unternehmen wie die IdN Immodaten GmbH bieten ein umfangreiches Datenangebot zum Immobilienmarkt an und werten dabei 26 Quellen auf Inserate von Tageszeitungen und Online-Börsen nach ihren einzelnen Bestandteilen aus (Stand Juni 2017), wobei hier die Nutzung kostenpflichtig ist.
Mit statistischer Software und dem Internet kann man sich auch selbst ein Bild der aktuellen Mietpreise verschaffen! Benötigt wird dazu RStudio, ein Internetbrowser und eine Webseite mit Wohnungsinseraten wie bspw.
immobilienscout24. Mit dem
CSS Selector Gadget - ein Point-and-Click-Tool zum Identifizieren von sog. HTML-Nodes - und dem R-Paket rvest
können Inhalte von Webseiten extrahiert und verarbeitet werden.
Hinweis: Da die Struktur einer Webseite im Zeitverlauf selten statisch ist, kann die fehlerfreie Funktion des Codes nur durch kontinuierliche Pflege gewährleistet werden. Außerdem kann der Betreiber der Webseite das (automatisierte) Extrahieren von Inhalten ausschließen. Nähere Informationen dazu findet man gewöhnlich in der Datei robots.txt der jeweiligen Webseite.
Datenbeschaffung
Die Webseite immobilienscout24 bietet eine Suchleiste, mit welcher bspw. innerhalb einer Stadt oder einer Postleitzahl Wohnungsinserate gefunden werden können. Auf einer Ergebnisseite befinden sich dann mehrere Inserate mit den wichtigsten Informationen zur Wohnung (Preis, Größe, …). Um eine Seite der Suchergebnisse anzusteuern, muss zunächst die stadt
und das zugehörige bundesland
definiert werden. Weiterhin ist es ratsam die Seitenanzahl der Suchergebnisse zu prüfen und festzulegen, damit das Extrahieren der Inserate nicht ins Leere läuft.
stadt <- "Wiesbaden"
bundesland <- "Hessen"
seiten <- 20
Als Nächstes werden die für die Suche relevanten URLs aus den vorherigen Eingaben generiert. Auch legt man Objekte fest, in denen die extrahierten Inhalte gespeichert werden sollen. Der gesamte Inhalt einer Seite der Suchergebnisse wird in der Liste seite
gespeichert. Ich möchte später aus den Anzeigen Informationen über titel
, lage
, miete
und groesse
der Wohnung gewinnen und lege für jedes Merkmal eine leere Liste an.
# URL Vektor
url <- rep(0, seiten)
# Stamm Adresse
stamm <- "https://www.immobilienscout24.de/"
# Suchergebnisse Seite 1
url[1] <- paste0(stamm,"Suche/S-2/Wohnung-Miete/", bundesland, "/",stadt)
# Suchergebnisse Seite 2 bis Seite 20
for (x in 2:seiten){
url[x] <- paste0(stamm,"Suche/S-2/P-", x, "/Wohnung-Miete/", bundesland, "/",stadt)
}
# URL-Inhalt extrahieren und in Liste speichern
seite <- list()
for (j in 1:seiten){
seite[[j]] <- xml2::read_html(url[j])
}
# Zu extrahierende Merkmale festlegen
titel <- list()
lage <- list()
miete <- list()
groesse <- list()
Nun folgt eine Schleife, welche pro Durchlauf eine Seite der Suchergebnisse extrahiert. Ich habe vorher mit dem CSS Selector Gadget alle relevanten HTML-Nodes identifiziert, die ich nun gezielt ansteuere. Bspw. versteckt sich hinter der Node .data-ng-non-bindable
der Titel des Wohnungsinserates.
for (i in 1:seiten) {
# Titel des Inserats
titel[[i]] <- seite[[i]] %>%
html_nodes(".data-ng-non-bindable") %>%
html_text()
titel[[i]] <- gsub("NEU","", titel[[i]])
# Adresse
lage[[i]] <- seite[[i]] %>%
html_nodes(".link-underline") %>%
html_text()
lage[[i]] <- gsub("\n","", lage[[i]])
lage[[i]] <- trimws(lage[[i]])
lage[[i]] <- lage[[i]][-c(1,2,3)]
# Kaltmiete
miete[[i]] <- seite[[i]] %>%
html_nodes(".result-list-entry__primary-criterion:nth-child(1) .font-line-xs") %>%
html_text() %>%
strtrim(100)
miete[[i]] <- gsub("\n","", miete[[i]])
miete[[i]] <- trimws(miete[[i]])
miete[[i]] <- gsub("€","", miete[[i]])
miete[[i]] <- gsub("\\.","", miete[[i]])
miete[[i]] <- gsub("\\,",".", miete[[i]])
miete[[i]] <- as.numeric(miete[[i]])
# Groesse
groesse[[i]] <- seite[[i]] %>%
html_nodes(".result-list-entry__primary-criterion:nth-child(2) .font-line-xs") %>%
html_text() %>%
strtrim(100)
groesse[[i]] <- gsub("\n"," ", groesse[[i]])
groesse[[i]] <- trimws(groesse[[i]])
groesse[[i]] <- gsub("([0-9]+).*","\\1",groesse[[i]])
groesse[[i]] <- as.numeric(groesse[[i]])
}
Datenaufbereitung
Die Listen werden nun in Vektoren umgewandelt und dann in einem Data Frame zusammengefasst. Aus den Merkmalen groesse
und miete
bilde ich den Quotienten preis_qm
und entferne, sofern vorhanden, fehlende Angaben. Anschließend erstelle ich das Merkmal price_cat
, welches für jedes Inserat eine Preiskategorie angibt.
# Listen in Vektoren umwandeln
titel <-unlist(titel)
lage <- unlist(lage)
miete <- unlist(miete)
groesse <- unlist(groesse)
# Neues Merkmal
preis_qm <- miete/groesse
# Data Frame erstellen
daten <- data.frame(titel, lage, miete, groesse, preis_qm)
# Fehlende Werte entfernen
daten <- subset(daten, (!is.na(daten$preis_qm)))
# Preiskategorie
daten$pricecat[daten$preis_qm>=6 & daten$preis_qm < 8] = 1
daten$pricecat[daten$preis_qm>=8 & daten$preis_qm < 10] = 2
daten$pricecat[daten$preis_qm>=10 & daten$preis_qm < 12] = 3
daten$pricecat[daten$preis_qm>=12 & daten$preis_qm < 15] = 4
daten$pricecat[daten$preis_qm>=15 & daten$preis_qm < 20] = 5
daten$pricecat[daten$preis_qm>=20] = 6
daten$pricecat <- factor(daten$pricecat,
levels=c("1","2", "3","4","5","6"),
labels=c("6 bis 8 Euro pro Quadratmeter",
"8 bis 10 Euro pro Quadratmeter",
"10 bis 12 Euro pro Quadratmeter",
"12 bis 15 Euro pro Quadratmeter",
"15 bis 20 Euro pro Quadratmeter",
"mehr als 20 Euro pro Quadratmeter"))
Datenanalyse
Am 18.06.2017 konnte ich für Wiesbaden 400 Inserate extrahieren. Unten ist ein Auszug aus diesem Datensatz dargestellt.
titel | lage | miete | groesse | preis_qm |
---|---|---|---|---|
SCHÖNE EINLIEGERWOHNUNG in RUHIGER,STADTNAHER LAGE ( WI-SONNENBERG/EIGENHEIM) | Sonnenberg, Wiesbaden | 900 | 57 | 15.789474 |
1-Zimmer-Wohnung Wiesbaden, sehr zentrale Lage | Schiersteiner Str., Südost, Wiesbaden | 700 | 22 | 31.818182 |
Schöne zwei Zimmer Penthouse- Wohnung in Wiesbaden, Dotzheim | Greifstraße 7 a, Dotzheim, Wiesbaden | 650 | 63 | 10.317460 |
Von privat. Helle Zweizimmer-Wohnung in Wiesbaden-Biebrich, Straße der Republik | Straße der Republik 2, Biebrich, Wiesbaden | 450 | 58 | 7.758621 |
Möblierte, schöne, geräumige zwei Zimmer Altbau-Wohnung in Wiesbaden, Südost | Raabestraße 1, Südost, Wiesbaden | 920 | 70 | 13.142857 |
Rheinufernähe, große 6 Zimmer Wohnung auf zwei Etagen | Kastel, Wiesbaden | 1500 | 190 | 7.894737 |
Total renovierte Singlewohnung | Biebrich, Wiesbaden | 505 | 44 | 11.477273 |
Exclusive neu renovierte 2,5 Zimmer Wohnung am Kurpark | Bierstadt, Wiesbaden | 1100 | 75 | 14.666667 |
Helle und ruhig gelegene 2-Zimmer-Wohnung mit Balkon seitlich Otto-Suhr-Ring | Steinernstraße, Kostheim, Wiesbaden | 490 | 49 | 10.000000 |
Helles Einzimmerappartement in Wiesbaden, Mitte | Frankenstraße, Mitte, Wiesbaden | 350 | 34 | 10.294118 |
Loreleiring: Eine schicke 3 - ZKBB sucht nette Mieter. | Rheingauviertel, Wiesbaden | 600 | 80 | 7.500000 |
720 <U+0080>, 67 m², 3 Zimmer | Biebrich, Wiesbaden | 720 | 67 | 10.746269 |
**Wunderbare Wohnung mit Terrasse und EBK** | Kostheim, Wiesbaden | 660 | 73 | 9.041096 |
Altbau-Dachgeschosswohnung mit Blick über Wiesbaden | Adelheidstraße, Mitte, Wiesbaden | 650 | 62 | 10.483871 |
Helle und geräumige 1-Zimmer Wohnung in Wiesbaden, Kastel | Römerstr., Kastel, Wiesbaden | 425 | 50 | 8.500000 |
Traumhafte 2-Zimmer-Wohnungen in Toplage mit Panoramablick - Erstbezug! | Schönbergstr., Dotzheim, Wiesbaden | 695 | 53 | 13.113208 |
650 <U+0080>, 56 m², 2 Room(s) | Nußbaumstraße 1, Südost, Wiesbaden | 650 | 56 | 11.607143 |
großzügig geschnittene 4 Zimmer Wohnung mit Gartennutzung | Mitte, Wiesbaden | 1250 | 145 | 8.620690 |
Mitten im Leben: Helle 4 Zimmer Wohnung im einem gepflegten Altbau fußläufig zum Zentrum | Mitte, Wiesbaden | 885 | 102 | 8.676471 |
Nerotal - sanierte Wohnung mit Balkon und Garage | Nordost, Wiesbaden | 920 | 83 | 11.084337 |
Für Mainz habe ich 332 Inserate erhalten.
titel | lage | miete | groesse | preis_qm |
---|---|---|---|---|
Schnuckeliges 2 Zimmer Haus mit Terrasse | Bretzenheim, Mainz | 780 | 73 | 10.684931 |
Schöne zwei Zimmer Wohnung in Mainz, Gonsenheim | Gonsenheim, Mainz | 640 | 64 | 10.000000 |
Helle Einraumwohnung mit Sonnenterrassennutzung mitten in der City | Altstadt, Mainz | 520 | 44 | 11.818182 |
Ruhige Traum Wohnung mit Luxusausstattung und Loggia im 3.OG Zollhafen.… Erstbezug | Neustadt, Mainz | 1750 | 119 | 14.705882 |
Studenten aufgepasst " Super WG. geeignete 2 Zimmerwohnung mit Gartennutzung | Gonsenheim, Mainz | 675 | 61 | 11.065574 |
Schöne zwei Zimmer Wohnung in Mainz, Oberstadt | Oberstadt, Mainz | 690 | 60 | 11.500000 |
Helles Studio Apartment, top saniert, möbliert & zentral | Kaiserstr, Neustadt, Mainz | 650 | 40 | 16.250000 |
Hochwertige Wohnung mit Blick ins Grüne in Haus mit vier Parteien in Mainz-Bretzenheim | Bretzenheim, Mainz | 1100 | 92 | 11.956522 |
Für Stadtliebhaber!Gepflegtes Aparment mit Einbauküche,Gemeinschaftsdachterrasse & Tiefgarage. | Klarastraße, Altstadt, Mainz | 500 | 38 | 13.157895 |
Attraktive, helle 2 Zimmer Wohnung mit 2 Balkone und Einbauküche | Hartenberg/Münchfeld, Mainz | 780 | 83 | 9.397590 |
Schön möbliertes 1-Zi. Appartement in Mainz, Hartenberg/Münchfeld | Richard Schirrmann Str., Hartenberg/Münchfeld, Mainz | 350 | 22 | 15.909091 |
In einem wunderschönen Neubauhaus, schöne 2KB, Einbauküche, Terrasse, Mainz-Neustadt | Zollhafen, Neustadt, Mainz | 950 | 70 | 13.571429 |
Wohnen im Grünen, stadtnah! | Katzenberg, Finthen, Mainz | 500 | 61 | 8.196721 |
1.390 <U+0080>, 92 m², 3 Zimmer | An der Karlsschanze, Oberstadt, Mainz | 1390 | 92 | 15.108696 |
Helle sonnige Dachgeschoßwohnung im Zweifamilienhaus in Mainz-Laubenheim | Enggäßchen, Laubenheim, Mainz | 690 | 65 | 10.615385 |
Schöne drei Zimmer Wohnung in Mainz, Altstadt | Walpodenstr., Altstadt, Mainz | 690 | 60 | 11.500000 |
schöne helle 7-Zimme-Altbauwohnung, geeignet für Arbeiten u. Wohnen | Kaiserstraße, Altstadt, Mainz | 1800 | 180 | 10.000000 |
Mainz-Mombach, sehr schönes helles Souterrain-Appartement | An der Brunnenstube 15/6, Mombach, Mainz | 376 | 28 | 13.428571 |
1-Zimmer Appartment - Nähe Uniklinik | Untere Zahlbacher Straße, Oberstadt, Mainz | 350 | 30 | 11.666667 |
Schöne, geräumige drei Zimmer Wohnung in Mainz, Ebersheim | Ebersheim, Mainz | 840 | 98 | 8.571429 |
Mit dem R Paket ggmap
werde ich nun die Mietpreise visualisieren. Zunächst mussten die Adressen der Inserate mit der Geocoding API von Google georeferenziert werden. Zum Teil enthalten die Inserate nur ungenaue Adressangaben, wie den Stadtteil mit der zugehörigen Postleitzahl. An allen Punkten, die die API lediglich mit der Postleitzahl assoziiert, entsteht so eine zu hohe Konzentration von Angeboten.
Für die grafische Darstellung verwende ich die Funktion stat_bin2d()
aus dem R-Paket ggplot2
, welche Beobachtungen innerhalb von Rechtecken zusammenfasst und eine Hitzekarte erstellt. Eine Konzentration von Inseraten mit niedriger bzw. hoher Kaltmiete wird durch die Farbe Gelb bzw. Rot dargestellt.
Unten befindet sich eine Karte mit den Kaltmieten der Angebote innerhalb Wiesbadens. Besonders im Norden von Wiesbaden (Nordost, Sonnenberg) sowie im Süden (Künstlerviertel, Südost) ist Wohnraum relativ teuer. In zentraler Lage (Mitte, Westend) sind die Wohnungen hingegen durchschnittlich etwas günstiger.
# Karte von Wiesbaden
karte_wi = qmap("Wiesbaden", zoom = 12, color = "bw",
source = "stadia", maptype = "stamen_toner_background",
darken = c(.3, "#BBBBBB"))
# Heatmap
karte_wi +
stat_bin2d(
aes(x = long, y = lati, fill = as.factor(pricecat)),
size = 1, bins = 20, alpha = 0.4, data = daten_wi
) +
scale_fill_brewer(type = "seq", palette = "YlOrRd") +
labs(caption = paste0(
dim(daten_wi)[1],
" extrahierte Inserate; Quelle: immobilienscout24.de")
) +
guides(fill = guide_legend(title="Kaltmiete"))
Es folgt die gleiche Darstellung für Mainz. Hier ist das Mietniveau in der Innenstadt (Neu- und Altstadt) besonders hoch. In den Randbezirken ist es dagegen im Durchschnit etwas günstiger.
# Karte von Mainz
karte_mz = qmap("Mainz", zoom = 12, color = "bw",
source = "stadia", maptype = "stamen_toner_background",
darken = c(.3, "#BBBBBB"))
# Heatmap
karte_mz +
stat_bin2d(
aes(x = long, y = lati, fill = as.factor(pricecat)),
size = 1, bins = 20, alpha = 0.4, data = daten_mz) +
scale_fill_brewer(type = "seq", palette = "YlOrRd") +
labs(caption = paste0(
dim(daten_mz)[1],
" extrahierte Inserate; Quelle: immobilienscout24.de")
) +
guides(fill = guide_legend(title="Kaltmiete"))
- Posted on:
- June 19, 2017
- Length:
- 8 minute read, 1639 words
- Categories:
- R Kartierung Webscraping
- See Also: