class: center, middle, inverse, title-slide # Web Scraping mit R ### Fabian Gülzau ### Humboldt-Universität zu Berlin ### 2019/05/17 --- # Inhalt 1. Einleitung 2. Web Scraping + Technologien des WWW + Web Scraping in R + a) Generelles Schema + b) Spezielle Technologien 3. Rechtliche & ethische Aspekte 4. Weitere Beispiele und Anwendungen 5. Weiterlernen --- # Zur Präsentation - Folien sind online verfügbar: https://github.com/FabianFox/Webscraping-Muenster-Slides - Folien herunterladen und im Browser öffnen - Beispiele auf [GitHub](https://github.com/FabianFox/Webscraping-Muenster-) - Benötigte Pakete über: ```r install.packages(pacman) # Installation nur einmal notwendig library(pacman) p_load(tidyverse, rvest, httr, robotstxt, qdap, janitor, devtools, lubridate) ``` --- # 1. Einleitung Ziele des Workshops: - Web Scraping als Methode - Grundlagen: WWW - Umsetzung in R - Beispiele - Quellen zum Selbststudium kennenlernen --- # 2. Web Scraping > "...something big is going on" ([Salganik 2018: 2](https://www.bitbybitbook.com/en/1st-ed/preface/)) - Übergang vom analogen zum digitalen Zeitalter - kommende Krise der empirischen Soziologie ([Savage & Burrows 2007](https://journals.sagepub.com/doi/10.1177/0038038507080443#articleShareContainer))? - neue Möglichkeiten und/oder Gefahren ([Salganik 2018: 17-41](https://www.bitbybitbook.com/en/1st-ed/observing-behavior/characteristics/)) > "'computational social science' (CSS) is occurring. The question is whether > it happens with or without social scientists" ([Heiberger & Riebling 2016: 1](https://journals.sagepub.com/doi/abs/10.1177/2059799115622763)) --- # Digitale Daten - positiv: big, always-on, nonreactive - negativ: incomplete, inaccessible, nonrepresentative, drifting, algorithmically confounded, dirty, sensitive Quelle: [Salganik 2018: 17-41](https://www.bitbybitbook.com/en/1st-ed/observing-behavior/characteristics/) --- # Technologien des WWW .pull-left[ Infrastruktur des Internets im Alltag irrelevant. Unser Browser übernimmt: - Serveranfragen (Request/Response: HTTP) - Darstellung von HTML, CSS und JavaScript Um Informationen gezielt abzufragen, benötigen wir allerdings basale Kenntnisse der zugrundeliegenden Technologien. ] .pull-right[ <img src="Figures/Greer-Seekabel.jpg" width="80%" /> ] --- # HTTP I **H**yper**t**ext **T**ransfer **P**rotocol [(HTTP)](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol) <img src="Figures/http-request-response.png" width="50%" /> - Übertragungsprotokoll, welches aus zwei Teilen besteht: - Request (Client) - Response (Server) --- # HTTP II - Requests erfolgen über **U**niform **R**esource **L**ocators [(URL)](https://en.wikipedia.org/wiki/URL) - Teile der URL können genutzt werden, um Informationen zu extrahieren <img src="Figures/http-url-structure.png" width="50%" /> --- # Beispiel: HTTP I - `GET`-Abfrage mit `httr::GET` und Antwort des Servers in R ```r # install.packages("httr") p_load(httr) response <- GET("https://www.wahlrecht.de/umfragen/index.htm") %>% print() ``` ``` ## Response [https://www.wahlrecht.de/umfragen/index.htm] ## Date: 2019-05-17 05:43 ## Status: 200 ## Content-Type: text/html ## Size: 40.1 kB ## <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://w... ## <html lang="de-DE"> ## ## <head> ## <meta http-equiv="expires" content="0"> ## <meta http-equiv="content-type" content="text/html; charset=UTF-8"> ## <meta name="description" content="Sonntagsfrage – Aktuelle Ergebni... ## <meta name="keywords" content="Wahlumfragen, Wahlumfrage, Wahlprognosen,... ## <meta property="og:site_name" content="Wahlrecht.de"> ## <meta property="og:url" content="https://www.wahlrecht.de/umfragen/index... ## ... ``` --- # Beispiel: HTTP II ```r #install.packages("rvest") library(rvest) survey <- response %>% read_html() %>% html_table(".wilko", header = TRUE, # first row: header fill = TRUE) %>% # fill with NA .[[2]] %>% # select data frame glimpse() ``` ``` ## Observations: 9 ## Variables: 12 ## $ Institut <chr> "Veröffentl.", "CDU/CSU", "SPD", "GRÜNE", "... ## $ `` <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA ## $ Allensbach <chr> "18.04.2019", "30 %", "18,5 %", "18 %", "9 ... ## $ Emnid <chr> "11.05.2019", "29 %", "16 %", "19 %", "9 %"... ## $ Forsa <chr> "13.05.2019", "30 %", "15 %", "20 %", "8 %"... ## $ `Forsch’gr.Wahlen` <chr> "10.05.2019", "30 %", "16 %", "20 %", "7 %"... ## $ GMS <chr> "09.05.2019", "29 %", "17 %", "19 %", "8 %"... ## $ Infratestdimap <chr> "02.05.2019", "28 %", "18 %", "20 %", "8 %"... ## $ INSA <chr> "13.05.2019", "28,5 %", "16 %", "19 %", "9,... ## $ Yougov <chr> "03.05.2019", "29 %", "18 %", "17 %", "9 %"... ## $ `` <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA ## $ `Bundes-tagswahl` <chr> "24.09.2017", "32,9 %", "20,5 %", "8,9 %", ... ``` --- # Beispiel: HTTP III <img src="WebScraping-Muenster_files/figure-html/ggplot2_aw-1.png" width="65%" /> --- # HTML **H**yper**t**ext **M**arkup **L**anguage [(HTML)](https://www.w3schools.com/html/default.asp) - einfacher Text mit weiteren Anweisungen (Tags, Attributes...) - wird vom Browser interpretiert und dargestellt HTML-Code ist über die Webentwicklungstools des Browsers verfügbar - Rechtsklick -> Seitenquelltext anzeigen --- # Beispiel: HTML I Einfache Seiten über Editor/Notepad++ erstellbar: ```r <!DOCTYPE html> <html> <head> <title>Workshop Web Scraping</title> </head> <body> <h1> Web Scraping mit R, Universität Münster</h1> <p>Dieser Kurs führt in das "Web Scraping" mit R ein. Er wird durch <a href="https://fguelzau.rbind.io/">Fabian Gülzau</a> geleitet.</p> </body> </html> ``` Der Browser interpretiert den Code und zeigt eine [Internetseite](http://htmlpreview.github.io/?https://github.com/FabianFox/Webscraping-Muenster-/blob/master/Code/HTML-Example.html) an. --- # Beispiel: HTML II HTML: - hat eine Baumstruktur - Markup-Elemente helfen uns Informationen zu lokalisieren (Relationalität) - **D**ocument **O**bject **M**odel [(DOM)](https://en.wikipedia.org/wiki/Document_Object_Model) - Darstellung von HTML in R (parsing) <img src="Figures/Wahlrecht-Tree.PNG" width="70%" /> --- # CSS **C**ascading **S**tyle **S**heet [(CSS)](https://www.w3schools.com/css/default.asp) - beinhaltet Informationen über den Stil der HTML-Seite - kann genutzt werden, um Informationen zu lokalisieren (CSS-Selektoren) <img src="Figures/css_example.PNG" width="30%" /> --- # JavaScript [JavaScript](https://www.w3schools.com/js/default.asp): "Programmiersprache des Internets" - macht Internetseiten dynamisch - Inhalte erscheinen erst nach Ausführung von JS - problematisch für unsere Scraper <img src="Figures/JavaScript-Example.PNG" width="50%" /> --- # 2a) Web Scraping: Generelles Schema Web Scraping-Projekte folgen einem generellen Schema: 1. Internetseite kennenlernen 2. Import von HTML-Dateien 3. Information isolieren 4. Iteration (loops) <img src="Figures/data_science_wflow.PNG" width="50%" /> Quelle: [Wickham & Grolemund (2018)](https://r4ds.had.co.nz/introduction.html) --- # Web Scraping in R <img src="Figures/rvest_hex.PNG" width="8%" /> .pull-left[ 1. Internetseite kennenlernen 2. Import von HTML-Dateien 3. Information isolieren 4. Iteration (loops) ] -- .pull-right[ Das generelle Schema lässt sich in R über das Paket [`rvest`](https://github.com/hadley/rvest) umsetzen: 1. u.a. [`robotstxt`](https://github.com/ropensci/robotstxt) 2. `read_html`: Import von HTML/XML-Dateien 3. Information isolieren - `html_nodes`: Extrahiert Teile des HTML-Dokument mit Hilfe von XPath/CSS-Selektoren - `html_text` / `html_attr` / `html_table`: Extrahiert Text, Attribute und Tabellen 4. `map` (package: [purrr](https://purrr.tidyverse.org/)): Iteration (loops) ] -- --- # Beispiel: Sonntagsfrage - **Sonntagsfrage** - von verschiedenen Umfrageinstituten erhoben ([Wahlrecht.de](https://www.wahlrecht.de/umfragen/)) - Archivfunktion (zeitlicher Verlauf) **Ziel**: Aktuelle Umfragen aller Institute herunterladen und kombinieren. <img src="Figures/Sonntagsfrage.PNG" width="50%" /> Quelle: [Tagesschau.de](https://www.tagesschau.de/inland/deutschlandtrend/) --- # (1) Kennenlernen der Internetseite Fragen, die beantwortet werden sollten: - statische oder dynamische Internetseite - statisch: [`rvest`](https://github.com/hadley/rvest) - dynamisch: [`RSelenium`](https://github.com/ropensci/RSelenium) - Web Scraping erlaubt - Terms of Service (ToS) - robots.txt --- # Wahlrecht: Kennenlernen der Internetseite Hierzu gehört: - Seitenquelltext: **statisch** - HTML-Tags der Zielinformation: **table / class: wilko** - robots.txt / Terms of Service: **keine relevanten Beschränkungen** ```r paths_allowed( paths = c("/index.htm","/allensbach.htm"), domain = c("wahlrecht.de") ) ``` ``` ## wahlrecht.de No encoding supplied: defaulting to UTF-8. ``` ``` ## [1] TRUE TRUE ``` --- # (2) Import von HTML-Dateien Internetseiten müssen in ein Format übersetzt werden, welches von R gelesen und bearbeitet werden kann (z.B. Baumstruktur). Benötigt wird: - URL - Request/Response-Paar --- # Wahlrecht: Import von HTML-Seiten Über `rvest::read_html`: ```r (html.page <- read_html("https://www.wahlrecht.de/umfragen/allensbach.htm")) ``` ``` ## {xml_document} ## <html lang="de-DE"> ## [1] <head>\n<meta http-equiv="expires" content="0">\n<meta http-equiv="c ... ## [2] <body>\n\n<div class="head">\n<table class="title" width="100%"><tr> ... ``` --- # (3) Information isolieren Zur Extraktion von Informationen nutzen wir die Baumstruktur von HTML: - [XPath](https://www.w3schools.com/xml/xpath_intro.asp) - [CSS-Selektoren](https://www.w3schools.com/csSref/css_selectors.asp) <img src="Figures/TreeStructureII.PNG" width="80%" height="85%" /> Quelle: [selfhtml.de](https://wiki.selfhtml.org/wiki/XML/Regeln/Baumstruktur) --- # XPath und CSS-Selektoren: Tools Wir konstruieren Selektoren selten manuell, da Anwendungen dies übernehmen. Tools: - [Selector Gadget](https://selectorgadget.com/) - Browser: [Webentwicklungstools](https://developer.mozilla.org/de/docs/Tools/Seiten_Inspektor) - Lerntools: [CSS Diner](https://flukeout.github.io/) --- # Wahlrecht: CSS-Selektor HTML-Tabellen lassen sich oft besonders leicht identifizieren, da sie das Tag "table" tragen: ```r (html.node <- html_nodes(html.page, css = ".wilko")) ``` ``` ## {xml_nodeset (1)} ## [1] <table class="wilko" align="center" cellpadding="2" cellspacing="3" ... ``` --- # Wahlrecht: Umwandeln in Text/Tabelle Wir sind selten am HTML-Tag interessiert, sondern an dem Inhalt: ```r html.table <- html.node %>% html_table(header = TRUE, fill = TRUE) %>% .[[1]] %>% # body .[4:nrow(.), c(1, 3:9)] %>% # subsetting glimpse() ``` ``` ## Observations: 71 ## Variables: 8 ## $ `` <chr> "18.04.2019", "27.03.2019", "19.02.2019", "23.01.201... ## $ `CDU/CSU` <chr> "30,0 %", "30,0 %", "30,0 %", "31,5 %", "29,0 %", "2... ## $ SPD <chr> "18,5 %", "18,0 %", "18,0 %", "16,5 %", "16,5 %", "1... ## $ GRÜNE <chr> "18,0 %", "19,0 %", "18,5 %", "18,0 %", "19,0 %", "1... ## $ FDP <chr> "9,0 %", "8,5 %", "8,0 %", "8,5 %", "8,5 %", "9,5 %"... ## $ LINKE <chr> "8,0 %", "8,5 %", "8,0 %", "8,5 %", "9,0 %", "9,0 %"... ## $ AfD <chr> "12,5 %", "12,0 %", "13,5 %", "13,0 %", "14,0 %", "1... ## $ Sonstige <chr> "4,0 %", "4,0 %", "4,0 %", "4,0 %", "4,0 %", "4,0 %"... ``` zum Subsetting: [Wickham (2019)](https://adv-r.hadley.nz/subsetting.html) --- # Exkurs: Regex .left-column[ <img src="Figures/RegexOrly.jpg" width="90%" height="95%" /> ] .right-column[ **Reg**ular **Ex**pression - zur Suche von Ausdrücken (patterns) in Text (strings) - in R z.B. über [`stringr`](https://stringr.tidyverse.org/index.html) ```r str_view(string = "Wir benötigen nicht den gesamten Text, sondern die 42.", pattern = "[:digit:]+") ```
] --- # Wahlrecht: Regex Die Umfrageergebnisse liegen als "strings" vor, sodass wir sie für die Datenanalyse in numerische Werte umwandeln müssen. ```r str_view(string = html.table$`CDU/CSU`[1:5], pattern = "[:digit:]+,?[:digit:]?") ```
--- # Datenaufbereitung Wir ersetzen zudem die Kommata durch Punkte und wandeln die Daten in ein "long"-Format um: ```r allensbach.df <- html.table %>% rename("Zeitpunkt" = 1) %>% # 1. Variable benennen mutate(Zeitpunkt = parse_datetime(Zeitpunkt, # 2. als Datum format = "%d.%m.%Y")) %>% mutate_if(is.character, str_extract, # 3a. Zahl entnehmen pattern = "[:digit:]+,?[:digit:]?") %>% mutate_if(is.character, str_replace, # 3b. Komma als Punkt pattern = ",", replacement = ".") %>% mutate_if(is.character, as.numeric) %>% # 3c. als Zahl gather(party, vote, -Zeitpunkt) %>% # 4. long format glimpse() # 5. ausgeben ``` ``` ## Observations: 497 ## Variables: 3 ## $ Zeitpunkt <dttm> 2019-04-18, 2019-03-27, 2019-02-19, 2019-01-23, 201... ## $ party <chr> "CDU/CSU", "CDU/CSU", "CDU/CSU", "CDU/CSU", "CDU/CSU... ## $ vote <dbl> 30.0, 30.0, 30.0, 31.5, 29.0, 28.0, 29.0, 31.5, 31.0... ``` --- # Visualisierung Zuletzt können wir die Ergebnisse der Sonntagsfrage visualisieren (Paket: [`ggplot2`](https://ggplot2.tidyverse.org/)): <img src="WebScraping-Muenster_files/figure-html/allensbach.fig-1.png" width="60%" /> --- # (4) Iteration Wird in den weiteren Anwendungen besprochen ([Code](https://github.com/FabianFox/Webscraping-Muenster-/blob/master/Code/Wahlrecht-Scraper.R)) <img src="WebScraping-Muenster_files/figure-html/all_surveys-1.png" width="60%" /> --- # Medienhype: Martin Schulz .left-column[ <img src="Figures/MEGA-Schulz.jpg" width="100%" /> ] .right-column[ Bundestagswahlkampf 2017 - Hoffnung auf SPD-Kanzlerschaft (Umfragehoch: ~30%) - gestützt durch (sozialen) Medienhype ("Schulz-Zug") - abrupter Einbruch nach Landtagswahlen (u.a. NRW) Frage: Hat die mediale Debatte das Umfragehoch beeinflusst? ] --- # Bundestagswahlkampf 2017 ```r election.df <- readRDS(gzcon(url("https://github.com/FabianFox/Webscraping-Muenster-/blob/master/Data/election2017.RDS?raw=true"))) ``` <img src="WebScraping-Muenster_files/figure-html/forsa.fig-1.png" width="60%" /> --- # Vorgehen 1. Anzahl der SpiegelOnline-Artikel, die "Martin Schulz" erwähnen - Scraping des [SPON-Archivs](https://www.spiegel.de/suche/?suchbegriff=) 2. (explorative) Prüfung des Zusammenhang: Medienaufmerksamkeit und Umfrageergebnis <img src="Figures/schulzzug.PNG" width="80%" /> --- # Scraping SPON - Informationen auf einer einzelnen Seite abfragen - Ausgangsseite mit Sucheinstellungen ([Link](https://www.spiegel.de/suche/?suchbegriff=Martin+Schulz&suchzeitraum=ab2005&suchbereich=header%2Ctitle%2Clead&fromDate=01.01.2005&quellenGroup=SPOX&quellenGroup=SP)) -- ```r # Date: .search-teaser div # Title: .search-teaser .headline # Teaser: .article-intro url <- "https://www.spiegel.de/suche/?suchbegriff=Martin+Schulz&suchzeitraum=ab2005&fromDate=01.01.2005&quellenGroup=SPOX&quellenGroup=SP" spon.df <- read_html(url) %>% # Import html_nodes(".search-teaser div") %>% # CSS-Selektoren html_text() ``` -- --- # Scraping SPON: Iteration Sobald unser Scraper für eine Seite funktionieren, können wir sie generalisieren: - Funktionen ([Wickham 2019](https://adv-r.hadley.nz/functions.html)) - Schleifen mit ´purrr::map´ ([Cheatsheet](https://www.rstudio.com/resources/cheatsheets/#purrr), [Wickham & Grolemund 2017](https://r4ds.had.co.nz/iteration.html)) -- <img src="Figures/purrr_fig.PNG" width="80%" /> -- --- # Scraping SPON: Iteration URL: - statischer Teil ("https://www.spiegel.de/suche/ (... ) &quellenGroup=SP") - dynamischer Teil ("&pageNumber=**3**") -- Kombination der Teile: ```r links.df <- tibble( links = paste0("https://www.spiegel.de/suche/?suchbegriff=Martin+Schulz&suchzeitraum=ab2005&fromDate=01.01.2005&quellenGroup=SPOX&quellenGroup=SP&pageNumber=", seq(1:180)) ) %>% glimpse() ``` ``` ## Observations: 180 ## Variables: 1 ## $ links <chr> "https://www.spiegel.de/suche/?suchbegriff=Martin+Schulz... ``` -- --- ### Funktionen <img src="Figures/function_fig.PNG" width="60%" /> -- ```r # Some data test <- tibble( var1 = c(1, 3, 5, 9), var2 = c(5, 5, 5, 5) ) # Function perc_fun <- function(x){ # formals x / sum(x) * 100 # body } # Apply map(test, perc_fun) # Loop ``` ``` ## $var1 ## [1] 5.555556 16.666667 27.777778 50.000000 ## ## $var2 ## [1] 25 25 25 25 ``` -- --- # Scraping SPON: Funktion Unsere Funktion, die Publikationsdatum, Titel und Teasertext extrahiert: ```r spon_scraper <- function(x) { page <- read_html(x) date <- page %>% html_nodes(".search-teaser div") %>% html_text() title <- page %>% html_nodes(".search-teaser .headline") %>% html_text() teaser <- page %>% html_nodes(".article-intro") %>% html_text() df <- tibble(date, title, teaser) } ``` --- # Scraping SPON: Funktion & Iteration ```r spon.df <- map_dfr( links.df$links, ~{ Sys.sleep(sample(seq(0, 3, 0.5), 1)) # friendly scraping spon_scraper(.x) }) ``` --- # Ergebnis [Daten](https://github.com/FabianFox/Webscraping-Muenster-/blob/master/Data/SPON-Data.RDS) ```r spon.df <- readRDS(gzcon(url("https://github.com/FabianFox/Webscraping-Muenster-/blob/master/Data/SPON-Data.RDS?raw=true"))) ``` <img src="WebScraping-Muenster_files/figure-html/load_spon-1.png" width="50%" /> --- # Medienhype: Martin Schulz <img src="WebScraping-Muenster_files/figure-html/schulz_final-1.png" width="70%" /> --- # 2b. Spezielle Technologien Spezielle Möglichkeiten & Herausforderungen: [**A**pplication **P**rogramming **I**nterface (API)](https://en.wikipedia.org/wiki/Application_programming_interface) - **erleichtert** den Zugriff auf Daten über spezifische Schnittstellen - Beispiele: [Twitter](https://developer.twitter.com/en.html), [Die Zeit](http://developer.zeit.de/index/), [Eurostat](https://ec.europa.eu/eurostat/web/json-and-unicode-web-services), [Wikipedia](https://github.com/Ironholds/WikipediR/) - in R: [rtweet](https://github.com/mkearney/rtweet), [diezeit](https://github.com/chgrl/diezeit), [eurostat](https://github.com/rOpenGov/eurostat), [WikipediR](https://github.com/Ironholds/WikipediR/) Dynamische Webseiten & bot blocking: - **erschwert** Zugriff, da Daten erst nach Nutzeranfragen erzeugt werden - Beispiele: Süddeutsche Zeitung (Archiv), ArtFacts, GIZ - viele soziale Medien --- # APIs: Steigende Relevanz [(Code)](https://github.com/FabianFox/Webscraping-Muenster-/blob/master/Code/programmableweb-Scraper.R) <img src="WebScraping-Muenster_files/figure-html/unnamed-chunk-9-1.png" width="70%" /> --- # 3. Rechtliche Aspekte > "Big data? Cheap. Lawyers? Not so much." (Pete Warden zit. in [Mitchell 2015: 217](http://shop.oreilly.com/product/0636920034391.do)) - rechtliche Grauzone - länderspezifisch (USA: strikt; Deutschland: liberal) - Terms of Service (ToS) beachten - robots.txt prüfen --- # Rechtliche Aspekte: Praxis **Ziel**: "friendly scraping" - Server nicht überlasten ([crawl delay](https://rud.is/b/2017/07/28/analyzing-wait-delay-settings-in-common-crawl-robots-txt-data-with-r/)) - `Sys.sleep` einbauen (~5-10 Sekunden) - zufällige Wartezeit, um menschliches Verhalten zu imitieren - Bot oder Mensch - "headless browsing" - [RSelenium](https://github.com/ropensci/RSelenium), [decapitated](https://github.com/hrbrmstr/decapitated), [splashr](https://github.com/hrbrmstr/splashr) oder [htmlunit](https://github.com/hrbrmstr/htmlunit) - API nutzen (Sammlung unter [programmableweb.com](https://www.programmableweb.com/)) - Seitenbetreiber kontaktieren --- # 5. Weitere Beispiele und Anwendungen - [studium.org](https://github.com/FabianFox/Webscraping-Muenster-/blob/master/Code/programmableweb-Scraper.R) - JavaScript-Seiten: [ArtFacts](https://github.com/FabianFox/Webscraping-Muenster-/blob/master/Code/ArtFacts-Scraper.R) - weitere Beispiele... (Transfermarkt, GIZ) - Diskussion studentischer Projekte und Ideen --- # 6. Weiterlernen Bücher: - Wickham & Grolemund (2017) R for Data Science [(online)](https://r4ds.had.co.nz/) - Healy (2018) Data Visualization [(online)](https://socviz.co/index.html) - Phillips (2018) YaRrr! The Pirate’s Guide to R [(online)](https://bookdown.org/ndphillips/YaRrr/) Interaktiv: - RStudioPrimers [(online)](https://rstudio.cloud/learn/primers) - swirl: Learn R, in R [(Installation)](https://swirlstats.com/students.html) Kurzanleitungen: - Cheatsheets [(online)](https://www.rstudio.com/resources/cheatsheets/) Weiterführend: - Sammlung von Lernressourcen auf RStudio [(online)](https://www.rstudio.com/resources/) - Lerncommunity: [TidyTuesdays](https://github.com/rfordatascience/tidytuesday)