Web Scraping mit R

Workshop an der Universität Hamburg

Fabian Gülzau (HU Berlin)

16 Januar 2019

Inhalt

“If programming is magic, then web scraping is wizardry” (Mitchell 2015: vii)

  1. Einleitung
  2. Technologien des WWW
  3. Web Scraping (Toolbox)
    1. Generelles Schema
    2. Spezielle Technologien
  4. Rechtliche Aspekte
  5. Weitere Beispiele und Anwendungen

Zur Präsentation

install.packages(pacman) # Installation nur einmal notwendig
library(pacman)
p_load(tidyverse, rvest, httr, robotstxt, eurostat, lubridate)

1. Einleitung

“…something big is going on” (Salganik 2018: 2)

“‘computational social science’ (CSS) is occurring. The question is whether it happens with or without social scientists” (Heiberger & Riebling 2016: 1)

Neue Möglichkeiten: Beispiele

Exemplarische Studien:

Zensur in China I

King et al. (2013):

Chacha: Chinesische Internetpolizei

Chacha: Chinesische Internetpolizei

Zensur in China II

Quelle: King et al. (2013: 335)

Lehre

Studium.org (Code):

Digitale Daten

Quelle: Salganik 2018: 17-41

2. Technologien des WWW

Infrastruktur des Internets im Alltag irrelevant.

Unser Browser übernimmt:

Um Informationen gezielt abzufragen, benötigen wir allerdings basale Kenntnisse der zugrundeliegenden Technologien.

HTTP I

Hypertext Transfer Protocol (HTTP)

HTTP II

Beispiel: HTTP I

#install.packages("httr")
library(httr)
response <- GET("https://www.wiso.uni-hamburg.de/fachbereich-sozoek/professuren/lohmann/team/hertel-florian.html") %>%
  print()
## Response [https://www.wiso.uni-hamburg.de/fachbereich-sozoek/professuren/lohmann/team/hertel-florian.html]
##   Date: 2019-01-16 08:48
##   Status: 200
##   Content-Type: text/html; charset=utf-8
##   Size: 30.9 kB
## <!DOCTYPE html><html class="v4" lang="de" xml:lang="de" xmlns="http://ww...
## <meta name="csrf-token" content="q7cZCDWLVdlnp6YkwuRgJIwWG4OHaidCLliAuh4...
## <p>Anmeldungen bitte per Mail.</p><h3>Kontakt</h3><div class="telefon">T...
## <p><strong>Seit 09/2016<br /></strong>Wissenschaftlicher Mitarbeiter, Pr...
## <p><strong>09/2015-08/2016<br /></strong>Max Weber Postdoctoral Fellow, ...
## <p><strong>09/2010-10/2015<br /></strong>Affiliated Fellow, Bremen Inter...
## <p><strong>11/2009-07/2015<br /></strong>Wissenschaftlicher Mitarbeiter,...
## <p><strong>05/2009-10/2009<br /></strong>Wissenschaftlicher Mitarbeiter,...
## <p><strong>03/2009<br /></strong>Diplom Soziologie, Freie Universität Be...
## <li>Soziale Ungleichheit und Stratifikation</li>
## ...

Beispiel: HTTP II

#install.packages("rvest")
library(rvest)

pubs <- response %>% 
  read_html() %>% 
  html_nodes("#4128883 li") %>% 
  html_text() %>% 
  tibble(pub = .) %>%
  mutate(year = as.numeric(
    str_extract(pub, pattern = "[:digit:]{4}"))) %>%
  group_by(year) %>%
  summarise(number = n())
year number
2009 1
2010 2
2011 1
2014 1
2015 2
2016 1
2017 2
2018 1

HTML

Hypertext Markup Language (HTML)

Beispiel: HTML I

Einfache Seiten über Editor/Notepad++ erstellbar:

<!DOCTYPE html>
<html>

<head>
<title>Workshop Web Scraping</title>
</head>

<body>
<h1> Web Scraping mit R, Universit&auml;t Hamburg</h1>
<p>Dieser Kurs f&uuml;hrt in das "Web Scraping" mit R ein. Er wird durch <a href="https://fguelzau.rbind.io/">Fabian G&uuml;lzau</a> geleitet.</p>

</body>
</html>

Der Browser interpretiert den Code und zeigt eine Internetseite an.

Beispiel: HTML II

HTML:

CSS

Cascading Style Sheet (CSS)

JavaScript

JavaScript: Programmiersprache des Internets

Quelle: The Pudding (2018)

3a. Web Scraping (Toolbox)

Web Scraping-Projekte folgen einem generellen Schema:

  1. Internetseite kennenlernen
  2. Import von HTML-Dateien
  3. Information isolieren
  4. Iteration (loops)

Quelle: Wickham & Grolemund (2018)

Web Scraping (Toolbox)

Das generelle Schema lässt sich in R über das Paket rvest umsetzen:

  1. Kennenlernen der Internetseite (u.a. 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): Iteration (loops)

Beispiel: Sonntagsfrage

Ziel: Aktuelle Umfragen aller Institute herunterladen und kombinieren.

Quelle: Tagesschau.de

(1) Kennenlernen der Internetseite

Fragen, die beantwortet werden sollten:

Wahlrecht: Kennenlernen der Internetseite

Hierzu gehört:

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:

Wahlrecht: Import von HTML-Seiten

Über rvest::read_html:

(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:

Quelle: selfhtml.de

XPath und CSS-Selektoren: Tools

Wir konstruieren Selektoren selten manuell, da Anwendungen dies übernehmen.

Tools:

Wahlrecht: CSS-Selektor

HTML-Tabellen lassen sich oft besonders leicht identifizieren, da sie das Tag “table” tragen:

(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:

html.table <- html.node %>%
  html_table(header = TRUE, fill = TRUE) %>%
  .[[1]] %>%                                 # body
  .[4:nrow(.), c(1, 3:9)] %>%                # subsetting
  glimpse()
## Observations: 67
## Variables: 8
## $ ``        <chr> "20.12.2018", "30.11.2018", "18.10.2018", "18.09.201...
## $ `CDU/CSU` <chr> "29,0 %", "28,0 %", "29,0 %", "31,5 %", "31,0 %", "3...
## $ SPD       <chr> "16,5 %", "17,0 %", "19,0 %", "19,5 %", "20,0 %", "2...
## $ GRÜNE     <chr> "19,0 %", "19,0 %", "15,0 %", "12,5 %", "12,5 %", "1...
## $ FDP       <chr> "8,5 %", "9,5 %", "8,5 %", "8,5 %", "9,0 %", "9,5 %"...
## $ LINKE     <chr> "9,0 %", "9,0 %", "9,0 %", "9,0 %", "9,0 %", "9,0 %"...
## $ AfD       <chr> "14,0 %", "13,5 %", "15,0 %", "15,0 %", "14,5 %", "1...
## $ Sonstige  <chr> "4,0 %", "4,0 %", "4,5 %", "4,0 %", "4,0 %", "4,0 %"...

Exkurs: Regex

Regular Expression

str_view(string = "Wir benötigen nicht den gesamten Text, 
         sondern nur die Zahl, welche sich im Text verbirgt: 42.",
         pattern = "[:digit:]+")

Wahlrecht: Regex

Die Umfrageergebnisse liegen als “strings” vor, sodass wir sie für die Datenanalyse in numerische Werte umwandeln müssen.

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:

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: 469
## Variables: 3
## $ Zeitpunkt <dttm> 2018-12-20, 2018-11-30, 2018-10-18, 2018-09-18, 201...
## $ party     <chr> "CDU/CSU", "CDU/CSU", "CDU/CSU", "CDU/CSU", "CDU/CSU...
## $ vote      <dbl> 29.0, 28.0, 29.0, 31.5, 31.0, 30.5, 33.0, 34.0, 34.0...

Visualisierung

Zuletzt können wir die Ergebnisse der Sonntagsfrage visualisieren (Paket: ggplot2):

ggplot(subset(allensbach.df, party != "Sonstige"),
       aes(x = Zeitpunkt, y = vote, colour = party)) +
  geom_line(size = 1.5) +
  scale_color_manual(values = c("#009EE0", "#000000", "#FFED00", "#64A12D",
                                "#BE3075", "#EB001F"),
                     guide = guide_legend(title = NULL)) +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  labs(title = "Sonntagsfrage: Wenn am nächsten Sonntag Bundestagswahl wäre...",
       x = "", y = "", caption = "Quelle: wahlrecht.de (Allensbach)") +
  theme_minimal()

(4) Iteration

Wird in den weiteren Anwendungen besprochen (Code)

3b. Spezielle Technologien

Spezielle Möglichkeiten & Herausforderungen:

Application Programming Interface (API)

Dynamische Webseiten & bot blocking:

APIs: Steigende Relevanz

Quelle: Eigene Darstellung (Code)

API: Eurostat

Eurostat bietet eine API für Datenbankabfragen an (Paket: eurostat)

Es gibt ein “Cheatsheet” für das Paket, welches das folgende Vorgehen beschreibt:

  1. Suche nach Daten (search_eurostat)
  2. Herunterladen der Daten (get_eurostat)
  3. Datenaufbereitung (tidyverse)

Beispiel: Eurostat

Arbeitslosenquote:

  1. Daten suchen
content <- search_eurostat(pattern = "unemployment rate", type = "dataset") %>%
  glimpse()
## Observations: 7
## Variables: 8
## $ title                         <chr> "Harmonised unemployment rates (...
## $ code                          <chr> "ei_lmhr_m", "lfst_r_lmdur", "lf...
## $ type                          <chr> "dataset", "dataset", "dataset",...
## $ `last update of data`         <chr> "10.01.2019", "22.10.2018", "22....
## $ `last table structure change` <chr> "10.01.2019", "18.07.2018", "18....
## $ `data start`                  <chr> "1983M01", "1999", "1999", "1995...
## $ `data end`                    <chr> "2018M12", "2017", "2017", "2017...
## $ values                        <chr> NA, NA, NA, NA, NA, NA, NA
  1. Daten herunterladen
unem.df <- get_eurostat(id = content$code[1],                  # Variable
                        time_format = "date",                  # Datumsformat
                        filters = list(s_adj = "NSA",          # Datenfilter
                                       indic = "LM-UN-T-TOT", 
                                       geo = "EU28"),
                        stringsAsFactors = FALSE) 
  1. Datenaufbereitung
# 3. Datenaufbereitung
unem.df %>%
  filter(time >= "2018-01-01") %>%
  ggplot() +
  geom_line(mapping = aes(x = time, y = values), stat = "identity") +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  theme_minimal() +
  labs(title = "Monatliche Arbeitslosenquote (EU28), 2018",
       caption = "Quelle: eurostat (LM-UN-T-TOT)",
       x = "", 
       y = "")

4. Rechtliche Aspekte

“Big data? Cheap. Lawyers? Not so much.” (Pete Warden zit. in Mitchell 2015: 217)

4. Rechtliche Aspekte in der Praxis

Ziel: “friendly scraping”

5. Weitere Beispiele und Anwendungen

Weitere Beispiele:

5. R-Setup