Web Scraping von Fußballergebnissen mit R und rvest
Use Case
Für das trainieren von Vorhersagemodellen werden Daten benötigt. Eine Möglichkeit für die Generierung von Daten ist das sogenannte Web Scraping bzw. Web Data Harvesting. Hierbei werden Daten von Websites extrahiert und in ein “maschinen-freundliches” Format lokal zur Verfügung gestellt.
Für Python gibt es für diese Aufgabe die library BeautifulSoup und für R existiert u.a. das Package rvest. In diesem Beitrag wird eine praktische Anwendung von rvest dargestellt. Für eine grundlegende Einführung in rvest sei an dieser Stelle auf den Beitrag aus dem RStudio-Blog und den Beitrag bei Analytics Vidhya verwiesen.
Ziel ist es, eine Funktion zu schreiben die zu einer vorgegebenen Kombination aus europäischer Top-Fußball-Liga, Spieltag und Saison die Ergebnisse der jeweiligen Partien herunterlädt. Datenquelle soll die Seite der Sportschau sein.
Lösungsdesign
Folgende Packages werden für den Import und die Aufbereitung der Daten verwendet.
library(dplyr)
library(rvest)
library(stringr)
Als Bestandteil des Lösungsdesign werden verschiedenen Funktionen verwendet. Die Hauptfunktion ist get_matchday_results
. Diese Funktion lädt mit rvest die zu einer vorgegebenen Kombination aus Liga (Bundesliga, Premier League, Primera Division, Serie A), Spieltag und Saison die Ergebnisse herunter. Hierfür werden verschiedene Hilfsfunktionen verwendet. Die Funktion create_matchday_url
erstellt die für die angegebene Kombination die Internetadresse der Sportschau-Seite, auf der die Ergebnisse angezeigt werden.
Nachfolgend werden die einzelnen Funktionen aufgeführt. Zu Beginn wird die Hilfsfunktion create_matchday_url
definiert. Sie setzt die URL für die angegebene Liga-Spieltag-Saison-Kombination zusammen. Die aufgerufene Seite ist die Datenquelle für die Extraktion. Es wird die mobile Seite der Sportschau aufgerufen, da diese weniger Informationen beinhaltet und leichter auszulesen ist.
create_matchday_url <- function(league, matchday, season){
# Setzen der Abkürzung in Abhängigkeit
if(league=="Premier League"){
league.id <- "ENG"
} else if(league == "Bundesliga"){
league.id <- "BL1"
} else if(league == "Primera Division"){
league.id <- "SPA"
} else if(league == "Serie A"){
league.id <- "ITA"
}
link <- paste0("http://m.sportschau.de/SportschauMobil/web/results/fb/",
league.id,"/",
season, "/1/",
matchday,"?sportart=Fußball")
return(link)
}
Bei dem Web Scraping kann es manchmal sein, dass bestimmte Zeichen aus dem heruntergeladenen String entfernt werden sollen. Dies erfolgt mit der Hilfsfunktionen trim_line_sportschau
und replace_character
trim_line_sportschau <- function(line){
line <- str_replace_all(line, "\t", "")
line <- str_replace_all(line, "\n", "")
line <- str_trim(line)
return(line)
}
replace_character <- function(x){
x <- str_replace(string = x, "ü", "ü")
x <- str_replace(string = x, "ö", "ö")
return(x)
}
Die Funktion get_matchday_results
lädt die Ergebnisse zu einer vorgegebenen Liga-Spieltag-Saison-Kombination von der Sportschau. Die Funktion lädt zuerst die relevante Seite mit read_html
herunter. Die Seite ist mit verschiedenen CSS-Tags versehen (z.B. <div class="begegnung">
und <div class="mannschaften">
. Das rvest Package nutzt diese Informationen um die entsprechenden Inhalte auszulesen. Im Anschluss werden die Ergebnisse in ein Dataframe mit den Spalten league, matchday, season, team.home, team.away, goals.home und goals.away Dataframe konvertiert.
get_matchday_results <- function(league, matchday, season){
# Rohdaten mit rvest
url <- create_matchday_url(league, matchday, season)
matchday.page <- read_html(url, encoding = "UTF-8")
matches <- matchday.page %>%
html_nodes(".begegnung") %>%
html_nodes(".mannschaften") %>%
html_text()
results <- matchday.page %>%
html_nodes(".begegnung") %>%
html_nodes(".ergebnis") %>%
html_text()
nr.matches <- length(matches)
# data processing
matchday.results <- data.frame(league = rep(league, nr.matches),
matchday = rep(matchday, nr.matches),
season = rep(season, nr.matches),
team.home = rep("Unknown", nr.matches),
team.away = rep("Unknown",nr.matches),
goals.home = rep(-1, nr.matches),
goals.away = rep(-1, nr.matches),
stringsAsFactors=FALSE)
for(i in 1:nr.matches){
match <- trim_line_sportschau(line = as.character(matches[i]))
match <- unlist(str_split(match, ":"))
matchday.results[i, "team.home"] <- str_trim(match[1])
matchday.results[i, "team.away"] <- str_trim(match[2])
result <- trim_line_sportschau(line = as.character(results[i]))
result <- unlist(str_split(result, ":"))
matchday.results[i, "goals.home"] <- str_trim(result[1])
matchday.results[i, "goals.away"] <- str_trim(result[2])
}
# Formatierungen
matchday.results <- matchday.results %>%
mutate(team.home = replace_character(team.home),
team.away = replace_character(team.away),
goals.home = ifelse(goals.home == "-",
NA, as.integer(goals.home)),
goals.away = ifelse(goals.home == "-",
NA, as.integer(goals.away)))
return(matchday.results)
}
Die Funktion get_league_results
dient der Bequemlichkeit und lädt alle Ergebnisse bis zu einem vorgegebenen Spieltag played
herunter. Hierbei wird mit der Funktion Sys.sleep
eine kleine, zufällige Verzögerung eingebaut, um nicht negativ bei Sportschau.de aufzufallen.
get_league_results <- function(league, played, season){
league.results <- data.frame(league = rep(league, 500),
matchday = rep(0, 500),
season = rep(season, 500),
team.home = rep("Unknown", 500),
team.away = rep("Unknown", 500),
goals.home = rep(-1, 500),
goals.away = rep(-1, 500),
stringsAsFactors=FALSE)
j <- 0
for(i in 1:played){
# Sys.Sleep
Sys.sleep(sample(seq(1, 2, by=0.2), 1))
# Download Spieltag
df <- get_matchday_results(league, i, season)
nr.matches <- dim(df)[1]
league.results[(j+1):(j+nr.matches), ] <- df
j <- j + nr.matches
}
league.results <- league.results %>%
filter(matchday != 0)
return(league.results)
}
Anwendung
Exemplarisch werden die Ergebnisse der Funktion get_matchday_results
für verschiedene Liga-Spieltag-Saison-Kombinationen dargestellt.
get_matchday_results("Bundesliga", 8, 2017) %>%
knitr::kable()
league | matchday | season | team.home | team.away | goals.home | goals.away |
---|---|---|---|---|---|---|
Bundesliga | 8 | 2017 | Hamburger SV | E. Frankfurt | 0 | 3 |
Bundesliga | 8 | 2017 | B. Leverkusen | Hoffenheim | 0 | 3 |
Bundesliga | 8 | 2017 | Hertha BSC | 1. FC Köln | 2 | 1 |
Bundesliga | 8 | 2017 | FC Ingolstadt | Bor. Dortmund | 3 | 3 |
Bundesliga | 8 | 2017 | Darmstadt 98 | VfL Wolfsburg | 3 | 1 |
Bundesliga | 8 | 2017 | SC Freiburg | FC Augsburg | 2 | 1 |
Bundesliga | 8 | 2017 | Bay. München | B. M´gladbach | 2 | 0 |
Bundesliga | 8 | 2017 | RB Leipzig | Werder Bremen | 3 | 1 |
Bundesliga | 8 | 2017 | FC Schalke 04 | FSV Mainz 05 | 3 | 0 |
Auch historisches Daten der Bundesliga ab der Saison 2007/2008 können so abgefragt werden.
get_matchday_results("Bundesliga", 6, 2008) %>%
knitr::kable()
league | matchday | season | team.home | team.away | goals.home | goals.away |
---|---|---|---|---|---|---|
Bundesliga | 6 | 2008 | VfL Bochum | E. Frankfurt | 0 | 0 |
Bundesliga | 6 | 2008 | Werder Bremen | VfB Stuttgart | 4 | 1 |
Bundesliga | 6 | 2008 | FC Schalke 04 | A. Bielefeld | 3 | 0 |
Bundesliga | 6 | 2008 | Hamburger SV | 1.FC Nürnberg | 1 | 0 |
Bundesliga | 6 | 2008 | Hertha BSC | Bor. Dortmund | 3 | 2 |
Bundesliga | 6 | 2008 | Ener. Cottbus | VfL Wolfsburg | 1 | 2 |
Bundesliga | 6 | 2008 | Hansa Rostock | MSV Duisburg | 2 | 0 |
Bundesliga | 6 | 2008 | Karlsruher SC | Bay. München | 1 | 4 |
Bundesliga | 6 | 2008 | Hannover 96 | B. Leverkusen | 0 | 3 |
Ebenfalls können auch ausländische Ligen heruntergeladen werden.
get_matchday_results("Premier League", 8, 2017) %>%
knitr::kable()
league | matchday | season | team.home | team.away | goals.home | goals.away |
---|---|---|---|---|---|---|
Premier League | 8 | 2017 | FC Chelsea | Leicester | 3 | 0 |
Premier League | 8 | 2017 | AFC Bournem. | Hull City | 6 | 1 |
Premier League | 8 | 2017 | FC Arsenal | Swansea City | 3 | 2 |
Premier League | 8 | 2017 | Manchester C. | FC Everton | 1 | 1 |
Premier League | 8 | 2017 | Stoke City | AFC Sunderl. | 2 | 0 |
Premier League | 8 | 2017 | West Bromwich | Tottenham | 1 | 1 |
Premier League | 8 | 2017 | Crystal Pal. | West Ham Utd. | 0 | 1 |
Premier League | 8 | 2017 | Middlesbrough | FC Watford | 0 | 1 |
Premier League | 8 | 2017 | Southampton | FC Burnley | 3 | 1 |
Premier League | 8 | 2017 | FC Liverpool | Manchester U. | 0 | 0 |
Sollen dagegen nicht nur einzelne Spieltage, sondern alle Ergebnisse bis zu einem Spieltag heruntergeladen werden, so kann die Funktion get_league_results
zum Einsatz kommen. Mit dem unten stehenden Code werden alle Ergebnisse der Bundesliga Saison 2016/2017 heruntergeladen und anschließend als csv-Datei exportiert.
get_league_results("Bundesliga", 34, 2017) %>%
write.table("bundesliga-ergebnisse-2017.csv", sep = ';', dec = ',', row.names = FALSE)