1  Datenerhebung

Für die automatische Erhebung von Online-Inhalten mit R gibt es verschiedene Möglichkeiten, die mit unterschiedlichem Aufwand verbunden sind. Manche Daten sind direkt für die Auswertung in R geeignet, z.B. tabellarische Daten wie CSV-Dateien, andere müssen mühsam aus Inhalten extrahiert werden, die für menschliche RezipientInnen aufbereitet sind, z.B. Webseiten. Für manche Plattformen und Datenformate gibt es fertige R-Pakete, für andere müssen wir selbst die Aufbereitung programmieren.

Aufsteigend von der leichtesten zur schwierigsten Umsetzung, natürlich abhängig von der Plattform, die untersucht werden soll, ergibt sich diese Reihe:

  1. maschinenlesbaren Dateien direkt aus dem WWW einlesen (z.B. CSV-Files)
  2. fertige R-Pakete für einzelne Plattformen/Anbieter verwenden (z.B. für TikTok)
  3. über Feeds oder API (Application Programming Interface) standardisierte Daten erhalten
  4. über Web-Scraping HTML-Inhalte von Websites herunterladen und verarbeiten
  5. über ferngesteuerte Browser Inhalte erheben oder Screenshots erstellen
  6. NutzerInnen um Datenspenden bitten

Die Punkte 5 und 6 werden wir nicht behandeln, weil sie einerseits sehr plattformabhängig sind, und andererseits eine sehr tiefgehende Auseinandersetzung mit den technischen und forschungspraktischen Herausforderungen erfordern, die den Rahmen des Seminars sprengen würden. Leider fallen einige sehr populäre Platformen wie Facebook, Instagram und Twitter darunter, die den Forschenden die Erhebung selbst von “öffentlichen” Daten sehr erschweren.

Zu Beginn laden wir zunächst das tidyverse Paket und setzen ein schöneres Theme für alle Grafiken, die wir erstellen.

library(tidyverse)
theme_set(theme_minimal())

1.1 Tabellarische Daten

1.1.1 CSV-Dateien

Am einfachsten ist die Situation, wenn Textdaten bereits in strukturierter maschinenlesbarer Form vorliegen. Im Internet gibt es zahlreiche mehr oder minder seriöse Quellen für solche Daten, etwa auf Kurs-Websites, bei Kaggle, oder auf dieser Workshop-Seite.

Wir haben hier einen Datensatz mit Songtexten von Taylor Swift ausgewählt, der im Rahmen der Tidy Tuesday Reihe für originelle Datenanalysen veröffentlicht wurde. Hier finden sich auch viele andere interessante Datensätze. Der Datensatz liegt direkt als CSV-File vor, das wir in R einlesen und ansehen können.

# Source:
taylor_swift_lyrics <- readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-09-29/taylor_swift_lyrics.csv")
taylor_swift_lyrics
# A tibble: 132 × 4
  Artist       Album        Title                  Lyrics                       
  <chr>        <chr>        <chr>                  <chr>                        
1 Taylor Swift Taylor Swift Tim McGraw             "He said the way my blue eye…
2 Taylor Swift Taylor Swift Picture to Burn        "State the obvious, I didn't…
3 Taylor Swift Taylor Swift Teardrops on my Guitar "Drew looks at me,\nI fake a…
4 Taylor Swift Taylor Swift A Place in This World  "I don't know what I want, s…
5 Taylor Swift Taylor Swift Cold As You            "You have a way of coming ea…
# ℹ 127 more rows

Die Songtexte sind in der Spalte Lyrics, zusätzlich gibt es noch Angaben zu Song und Album.

1.1.2 Andere Formate

Manchmal liegen Daten in weniger verbreiteten Formaten wie SPSS oder Stata-Datensätze oder, wie im folgenden Beispiel, als sog. parquet-File vor. Auf der Plattform Hugging Face hat ein Nutzer die Inhalte der Tagesschau-Website per Webscraping (s.u.) heruntergeladen und aufbereitet. Eine kurze Web-Recherche ergab, dass für das Einlesen dieses Datenformats das R-Paket arrow zur Verfügung steht. Dieses laden wir (nachdem wir es installiert haben, was RStudio uns vorschlagen sollte) und öffnen dann die Datei direkt über die URL.

library(arrow)
# Source: https://huggingface.co/datasets/bjoernp/tagesschau-2018-2023
tagesschau_archiv <- arrow::read_parquet("https://huggingface.co/api/datasets/bjoernp/tagesschau-2018-2023/parquet/default/train/0.parquet")
tagesschau_archiv
# A tibble: 21,847 × 6
  date       headline                    short_headline short_text article link 
  <chr>      <chr>                       <chr>          <chr>      <chr>   <chr>
1 2023-04-27 Türkei-Wahl in Deutschland… 1,5 Millionen… "Etwa 1,5… "Etwa … /aus…
2 2023-04-27 Bolsonaro bestreitet Verwi… Brasilien      "Brasilie… "Brasi… /aus…
3 2023-04-27 Streiten, ob Hilfe wirklic… Afghanistan-P… "Deutschl… "Deuts… /inl…
4 2023-04-27 Republikaner machen Druck … US-Haushaltss… "Die Repu… "Die R… /aus…
5 2023-04-27 Russland plant Schein-Orga… Strategiepapi… "Russland… "Russl… /inv…
# ℹ 21,842 more rows

Der Datensatz enthält über 20.000 Beiträge, und neben dem Veröffentlichungsdatum die Überschriften und Texte der Artikel, je in einer kurzen und langen Fassung.

Schlagzeilen im Zeitverlauf

Mit dem nachfolgenden Code zählen wir das Vorkommen von Ukraine in der Spalte short_text des Datensatzes, und erstellen aus der Häufigkeitstabelle ein Liniendiagramm, in dem die Beiträge pro Woche abgetragen werden. Die zentrale Funktion ist str_detect(), mit dem codiert wird, ob ein Begriff im Text vorkommt oder nicht. Dieser Wert wird in der Variable ukraine gespeichert und anschließend wochenweise mit group_by() und summarise() aufsummiert. Die Wochenvariable week wird mit floor_date() erstellt, die jedes Datum umcodiert in den ersten Wochentag der entsprechenden Kalenderwoche.

tagesschau_archiv |>
  mutate(
    ukraine = str_detect(short_text, "Ukrain|ukrain"),
    week = floor_date(as.Date(date), "week")
  ) |>
  group_by(week) |>
  summarise(n_ukraine = sum(ukraine)) |>
  ggplot(aes(x = week, y = n_ukraine)) +
  geom_line() +
  scale_x_date(
    date_breaks = "1 year",
    date_labels = "%Y"
  ) +
  labs(
    x = "", y = "Ukraine-bezogene Beiträge pro Woche",
    title = "Ukraine-Berichterstattung in der Tagesschau"
  )

Aufgabe

Erstellen Sie die gleiche Grafik für einen anderen Suchbegriff.

1.2 Spezifische R-Pakete

Neben unzähligen Paketen für spezielle statistische Verfahren, Datenmanagement oder Visualisierung gibt es auch für viele Online-Dienste R-Pakete, mit denen Daten dieser Dienste komfortable heruntergeladen, modifiziert und z.T. auch wieder hochgeladen werden können. Eine unvollständige und z.T. auch veraltete Übersicht gibt es im CRAN Task Web Technologies, wo auch ein Unterpunkt für Social Media Clients zu finden ist. Leider sind einige Pakete, wie etwa Rfacebook oder instaR durch zunehmden Restriktionen und Veränderungen der Plattformen obsolet. Wir wählen daher zwei, die aktuell noch funktionieren, Mastodon und TikTok.

1.2.1 Mastodon-Posts

Die Mastodon-Plattform wird als Alternative zu Twitter/X vor allem für das sog. Microblogging benutzt. Die ARD betreibt eine eigene Instanz, um dort ihre Inhalte zu verbreiten. Wir können die öffentlichen Posts (oder Toots) ganz leicht mit dem rtoot Paket herunterladen.

library(rtoot)
ard_toots <- rtoot::get_timeline_public(instance = "ard.social")
ard_toots
# A tibble: 20 × 29
  id         uri   created_at          content visibility sensitive spoiler_text
  <chr>      <chr> <dttm>              <chr>   <chr>      <lgl>     <chr>       
1 111368582… http… 2023-11-07 09:35:02 "<p>Sä… public     FALSE     ""          
2 111368576… http… 2023-11-07 09:33:39 "<p>Er… public     FALSE     ""          
3 111368550… http… 2023-11-07 09:27:01 "<p>Di… public     FALSE     ""          
4 111368523… http… 2023-11-07 09:20:05 "<p>It… public     TRUE      ""          
5 111368483… http… 2023-11-07 09:10:00 "<p>De… public     FALSE     ""          
# ℹ 15 more rows
# ℹ 22 more variables: reblogs_count <int>, favourites_count <int>,
#   replies_count <int>, url <chr>, in_reply_to_id <chr>,
#   in_reply_to_account_id <chr>, language <chr>, text <lgl>,
#   application <I<list>>, poll <I<list>>, card <I<list>>, account <list>,
#   reblog <I<list>>, media_attachments <I<list>>, mentions <I<list>>,
#   tags <I<list>>, emojis <I<list>>, favourited <lgl>, reblogged <lgl>, …

Neben dem eigentlichen Inhalt erhalten wir eine große Anzahl an Metadaten, u.a. auch Popularitätsindikatoren wie die Anzahl replies oder favourites.

1.2.2 TikTok-Videos

Das Paket traktok ist z.Z. nicht auf CRAN, daher müssen wir es manuell mit der install_github() Funktion installieren. Außerdem benötigen wir einen Browser-Cookie, da die TikTok-Website selbst ohne Login einen entsprechenden Cookie erfordert. Dieser ist als Datei unter data/tt_cookie.txt hinterlegt und wird zunächst eingelesen.

remotes::install_github("JBGruber/traktok", dependencies = F)
library(traktok)
traktok::tt_get_cookies(x = "data/tt_cookie.txt")

Als erstes laden wir mit tt_user_videos() eine Liste der letzten Videos eines Accounts herunter. Aufgrund sog. Rate-Limits seitens TikTok kann dies mal länger, mal kürzer dauern.

ed <- traktok::tt_user_videos("https://www.tiktok.com/@edsheeran")
ed
# A tibble: 35 × 2
  user_id   `video_urls <- ...`                                        
  <chr>     <chr>                                                      
1 edsheeran https://www.tiktok.com/@edsheeran/video/7284119424620137760
2 edsheeran https://www.tiktok.com/@edsheeran/video/7270920001131957536
3 edsheeran https://www.tiktok.com/@edsheeran/video/7256495610889653530
4 edsheeran https://www.tiktok.com/@edsheeran/video/7296153034474736928
5 edsheeran https://www.tiktok.com/@edsheeran/video/7293926597944462625
# ℹ 30 more rows

Wie wir sehen, ist das Ergebnis ein recht sparsamer Datensatz, in dem eigentlich nur die URLs der einzelnen Videos zu finden sind. Ein oder mehrere Videos samt Metadaten können wir über tt_videos() herunterladen, wobei standardmäßig die Videos selbst nicht gespeichert werden.

ed_video1 <- traktok::tt_videos("https://www.tiktok.com/@edsheeran/video/7289455403551804704")
ed_video1 |>
  glimpse()
Rows: 1
Columns: 16
$ video_id              <chr> "7289455403551804704"
$ video_url             <chr> "https://www.tiktok.com/@edsheeran/video/7289455…
$ video_timestamp       <dbl> 1697208599
$ video_length          <int> 22
$ video_title           <chr> "I love doing this so much. Thanks @Save The Mus…
$ video_locationcreated <chr> "GB"
$ video_diggcount       <int> 30200
$ video_sharecount      <int> 104
$ video_commentcount    <int> 302
$ video_playcount       <int> 334200
$ video_description     <chr> "I love doing this so much. Thanks @Save The Mus…
$ video_fn              <chr> "./edsheeran_video_7289455403551804704.mp4"
$ author_username       <chr> "edsheeran"
$ author_name           <chr> "Ed Sheeran"
$ download_url          <chr> "https://v16-webapp-prime.tiktok.com/video/tos/u…
$ html_status           <int> 200

Auch hier werden neben die Informationen zum Video wie Titel und Länge auch Metadaten und Indikatoren wie die Anzahl an Views mitgeliefert. Dies ist allerdings nur eine kleine Auswahl an Informationen, die der Autor des Pakets für besonders relevant erachtet. TikTok selbst liefert viele hundert weitere Spalten mit, die allerdings nur etwas aufwändiger aus dem JSON-Format (s.u.) extrahiert werden müssen.

TikTok-Coverbilder sammeln

Der folgende Code erstellt ein animiertes GIF-Bild aus den Coverbildern (Thumbnails) der letzten Videos eines Accounts. Da die URLs der Bilder nicht über tt_videos() zur Verfügung stehen, wird die Low-Level-Funktion tt_json() aufgerufen, die ein umfangreicheres Objekt zurückliefert. Darin verbirgt sich im Unterbereich video die cover Information, die wir als Liste erhalten. Diese wird anschließend mit dem magick Paket verarbeitet, das für Bilder aller Art geeignet ist: Alle Bilder der Liste werden eingelesen, einheitlich verkleinert und schließlich als GIF-Bild mit 1 Sekunde Pause pro Frame abgespeichert.

library(magick)
library(gifski)
traktok::tt_json("https://www.tiktok.com/@edsheeran")$ItemModule |>
  map("video") |>
  map_chr("cover") |>
  magick::image_read() |>
  magick::image_resize("320x") |>
  magick::image_write_gif("tiktok_ed.gif", delay = 1)
magick::image_read("tiktok_ed.gif")

Aufgabe

Erstellen Sie eine GIF-Zusammenfassung für einen anderen TikTok-Account.

1.3 Feeds und Web-APIs

Mit Feeds und JSON-APIs stehen seit langem standardisierte Zugriffsmechanismen für Web-Inhalte zur Verfügung. Diese sind mal mehr (RSS-Feeds), mal weniger (JSON) standardisiert: RSS-Feeds haben stets dieselben Spalten wir title oder description, während das JSON-Format syntaktisch zwar immer gleich ist, die enthaltenen Felder aber unterschiedlich sind. Daher müssen wir JSON-Daten immer manuell inspizieren, um relevante Felder zu finden, während Feeds leichter zu verarbeiten sind.

1.3.1 RSS Feeds

Für das Einlesen von Feeds gibt es das tidyRSS Paket mit dem entsprechenden Befehl tidyfeed(), dem wir nur die URL des Feeds übergeben müssen. Hier nehmen wir die Schlagzeilen von SPIEGEL Online.

library(tidyRSS)
spiegel <- tidyRSS::tidyfeed("https://www.spiegel.de/schlagzeilen/tops/index.rss")
spiegel |>
  glimpse()
Rows: 20
Columns: 14
$ feed_title           <chr> "DER SPIEGEL - Schlagzeilen – Tops", "DER SPIEGEL…
$ feed_link            <chr> "https://www.spiegel.de/", "https://www.spiegel.d…
$ feed_description     <chr> "Deutschlands führende Nachrichtenseite. Alles Wi…
$ feed_language        <chr> "de", "de", "de", "de", "de", "de", "de", "de", "…
$ feed_pub_date        <dttm> 2023-11-07 10:38:30, 2023-11-07 10:38:30, 2023-1…
$ feed_last_build_date <dttm> 2023-11-07 10:38:30, 2023-11-07 10:38:30, 2023-1…
$ feed_category        <chr> "Wirtschaft", "Wirtschaft", "Wirtschaft", "Wirtsc…
$ item_title           <chr> "Gasversorgung in Deutschland: Darum ist Deutschl…
$ item_link            <chr> "https://www.spiegel.de/wirtschaft/service/gasver…
$ item_description     <chr> "Die Angst vor Gasknappheit bestimmte den letzten…
$ item_pub_date        <dttm> 2023-11-07 00:45:53, 2023-11-07 06:09:33, 2023-1…
$ item_guid            <chr> "https://www.spiegel.de/wirtschaft/service/gasver…
$ item_enclosure       <list> [], [], [], [], [], [], [], [], [], [], [], [], …
$ item_category        <chr> "Wirtschaft", "Politik", "Ausland", "Ausland", "A…

Interessant sind für uns vor allem die Item-Variablen, etwa item_title oder item_description, die sich auf die einzelnen Beiträg beziehen. Ebenso gibt es wieder Metadaten wie etwas das Publikationsdatum oder die Kategorie.

1.3.2 JSON-APIs

Für das Einlesen von JSON-Daten gibt es das jsonlite Paket mit der fromJSON() Funktion. Als Beispiel rufen wir die offizielle JSON-API der Tagesschau auf, die hier dokumentiert ist. Die eigentlichen Nachrichten sind im Feld news, das wir in einen Datenframe konvertieren können.

library(jsonlite)
# Source: https://github.com/AndreasFischer1985/tagesschau-api
tagesschau_news <- jsonlite::fromJSON("https://www.tagesschau.de/api2/news")$news |>
  as_tibble()
tagesschau_news
# A tibble: 122 × 24
  sophoraId        externalId title date  teaserImage$title tags  updateCheckUrl
  <chr>            <chr>      <chr> <chr> <chr>             <lis> <chr>         
1 ofarim-verleumd… tagesscha… Säng… 2023… "Gil Ofarim (M),… <df>  https://www.t…
2 liveblog-israel… 222eea00-… ++ W… 2023…  <NA>             <df>  https://www.t…
3 ndr-news-blog-z… tagesscha… News… 2023… "Boris Herrmann … <df>  https://www.t…
4 video-1269700    f10f9aa1-… Euro… 2023… "Sendungsbild"    <df>  https://www.t…
5 video-1269698    f22eb9e1-… Aktu… 2023… "Sendungsbild"    <df>  https://www.t…
# ℹ 117 more rows
# ℹ 21 more variables: teaserImage$copyright <chr>, $alttext <chr>,
#   $imageVariants <df[,12]>, $type <chr>, tracking <list>, topline <chr>,
#   firstSentence <chr>, details <chr>, detailsweb <chr>, shareURL <chr>,
#   geotags <list>, regionId <int>, regionIds <list>, type <chr>,
#   breakingNews <lgl>, brandingImage <df[,5]>, streams <df[,4]>,
#   alttext <chr>, copyright <chr>, ressort <chr>, comments <chr>

Auch hier gibt es zahlreiche Spalten, u.a. title oder date.

Bildergallerie aus Sportmeldungen

Über die Tagesschau-API lassen sich auch spezifische Ressorts oder regionale Nachrichten herausfiltern. Hier speichern wir zunächst die Sportnachrichten als Datenframe ab. Anschließend extrahieren wir die Liste von Teaser-Bildern im kleinsten Format, löschen fehlende Einträge und verarbeiten die ersten 15 Bilder wieder mit dem Paket magick weiter, mit dessen Hilfe die Bilder eingelesen und zu einem Bildraster zusammengeführt werden.

sport_news <- jsonlite::fromJSON("https://www.tagesschau.de/api2/news?ressort=sport")$news |>
  as_tibble()

sport_news$teaserImage$imageVariants$`1x1-144` |>
  na.omit() |>
  head(15) |>
  magick::image_read() |>
  magick::image_montage(tile = 5, geometry = "x120+2+2")

Aufgabe

Erstellen Sie eine Gallerie für ein anderes Ressort oder ein Bundesland.

1.4 Web-Scraping

Wenn die für uns relevanten Informationen nicht in einem einfachen maschinenlesbaren Format vorliegen, müssen wir sie aus dem Quellcode der Webseite extrahieren. Diese besteht aus sog. HTML-Markup, das viele einzelne Elemente definiert. Die Herausforderung beim Web-Scraping besteht darin, die richtigen Elemente zu finden und zu extrahieren. Der Aufwand hierfür schwankt von Website zu Website. Wir verwenden dafür das Paket rvest, mit dem zumindest technisch der Zugriff auf verschiedene HTML-Element recht leicht ist.

1.4.1 HTML-Tabellen

Am einfachsten haben wir es, wenn die Informationen bereits als HTML-Tabelle, d.h. im Element <table> vorliegen. Dies ist z.B. bei vielen Wikipedia-Artikeln der Fall. Hier wollen wir die nach American Film Institute wichtigstens 100 Filme als Tabelle in R einlesen. Dazu lesen wir zunächst mit read_html() die entsprechende Wikipedia-Seite ein und geben das Ergebnis weiter an die Funktion html_table(), die alle HTML-Tabellen extrahiert.

library(rvest)
afi_list <- rvest::read_html("https://en.wikipedia.org/wiki/AFI%27s_100_Years...100_Movies_(10th_Anniversary_Edition)") |>
  rvest::html_table()

afi_list
[[1]]
# A tibble: 14 × 2
  X1    X2          
  <chr> <chr>       
1 1998  100 Movies  
2 1999  100 Stars   
3 2000  100 Laughs  
4 2001  100 Thrills 
5 2002  100 Passions
# ℹ 9 more rows

[[2]]
# A tibble: 100 × 6
   Rank `10th anniversary list (2007)` Director      Year `Production companies`
  <dbl> <chr>                          <chr>        <int> <chr>                 
1     1 Citizen Kane                   Orson Welles  1941 Mercury Productions, …
2     2 The Godfather                  Francis For…  1972 Paramount Pictures, A…
3     3 Casablanca                     Michael Cur…  1942 Warner Bros.          
4     4 Raging Bull                    Martin Scor…  1980 Chartoff-Winkler Prod…
5     5 Singin' in the Rain            Gene Kelly,…  1952 Metro-Goldwyn-Mayer   
# ℹ 95 more rows
# ℹ 1 more variable: `Change from 1998` <chr>

[[3]]
# A tibble: 1 × 2
  vteAFI's 100 Years...100 Movies (10th Anniversary Edi…¹ vteAFI's 100 Years..…²
  <chr>                                                   <chr>                 
1 "Citizen Kane (1941)\nThe Godfather (1972)\nCasablanca… "Citizen Kane (1941)\…
# ℹ abbreviated names:
#   ¹​`vteAFI's 100 Years...100 Movies (10th Anniversary Edition)`,
#   ²​`vteAFI's 100 Years...100 Movies (10th Anniversary Edition)`

Wir sehen, dass es 3 Tabellen auf der Wikipedia Seite gibt, und wir eigentlich nur die 2. Tabelle wollen, daher wählen wir nur diese aus.

afi_list[2]
[[1]]
# A tibble: 100 × 6
   Rank `10th anniversary list (2007)` Director      Year `Production companies`
  <dbl> <chr>                          <chr>        <int> <chr>                 
1     1 Citizen Kane                   Orson Welles  1941 Mercury Productions, …
2     2 The Godfather                  Francis For…  1972 Paramount Pictures, A…
3     3 Casablanca                     Michael Cur…  1942 Warner Bros.          
4     4 Raging Bull                    Martin Scor…  1980 Chartoff-Winkler Prod…
5     5 Singin' in the Rain            Gene Kelly,…  1952 Metro-Goldwyn-Mayer   
# ℹ 95 more rows
# ℹ 1 more variable: `Change from 1998` <chr>

Der Datenframe entspricht genau der HTML-Tabelle auf der Seite.

1.4.2 HTML Scraping

Eine typische Aufgabe für Web-Scraping ist das Extrahieren von Überschriften und Texten aus Nachrichtenseiten, wie dies der Nutzer im o.g. Beispiel der Tagesschau gemacht hat. Wir wollen hier die Schlagzeilen der Heute-Website extrahieren. Eine ausführliche Inspektion der Seite ergibt, dass die Schlagzeilen im Element h3 mit der Klasse teaser-title liegen. Wir extrahieren alle HTML-Elemente dieser Klasse mit der Funktion html_elements().

heute_headlines <- rvest::read_html("https://www.heute.de") |>
  rvest::html_elements("h3.teaser-title")
length(heute_headlines)
[1] 78

Aktuell gibt es 78 dieser Elemente auf der Heute-Website. Die eigentlichen Texte können wir mit html_text() erhalten, mit str_squish() entfernen wir die von der Redaktion offenbar sehr großzügig vergebenen Leerzeichen innerhalb der Schlagzeilen.

heute_headlines |>
  rvest::html_text() |>
  str_squish() |>
  head()
[1] "Bund und Länder einig Scholz: Bund zahlt 7.500 Euro pro Flüchtling"
[2] "Bund-Länder-Pakt Turbo für Bauvorhaben: Umweltverbände besorgt"    
[3] "Umfrage Jeder vierte Deutsche fühlt sich einsam"                   
[4] "Krieg in Nahost"                                                   
[5] "Luftangriffe und Kämpfe"                                           
[6] "Israel und Palästina Das Problem mit der Zwei-Staaten-Lösung"      

Wollen wir nun statt der Texte die URLs der einzelnen Beiträge bekommen, müssen wir in das Unterelement <a> und dessen Attribut href schauen. Dort sind in HTML die URLs einer Verlinkung hinterlegt. Diese URLs könnten wir in einem nächsten Schritt nun auch wieder per Web-Scraping weiterverarbeiten.

heute_headlines |>
  rvest::html_elements("a") |>
  rvest::html_attr("href") |>
  head()
[1] "/nachrichten/politik/migration-gipfel-bund-laender-scholz-100.html"                
[2] "/nachrichten/politik/bund-laender-pakt-bau-beschleunigung-umweltverbaende-100.html"
[3] "/nachrichten/panorama/einsamkeit-deutschland-umfrage-100.html"                     
[4] "/nachrichten/politik/zwei-staaten-loesung-palaestina-israel-100.html"              
[5] "/nachrichten/politik/baerbock-g7-japan-feuerpause-gaza-israel-100.html"            
[6] "/nachrichten/politik/usa-u-boot-atomwaffen-naher-osten-100.html"                   
Anteil Paywall-Artikel in Le Monde

Ein einfacher Anwendungsfall für Web-Scraping ist das Zählen bestimmter Elemente, etwa wie viele Überschriften oder Links es auf einer Seite überhaupt gibt. Im folgenden bestimmen wir den Anteil an Bezahl-Artikel (Paywall) auf der Website der französischen Zeitung Le Monde. Warum genau diese? Weil es sehr leicht funktioniert: Auf der Website werden alle Paywall-Artikel mit einem bestimmten Unterelement markiert (span.sr-only), d.h. wir können einfach alle Artikel und Paywall-Artikel zählen und diese ins Verhältnis setzen.

lemonde <- rvest::read_html("https://www.lemonde.fr/")

articles <- lemonde |>
  rvest::html_elements("div.article")

paywalled <- lemonde |>
  rvest::html_elements("div.article span.sr-only")

paywall_share <- length(paywalled) / length(articles)
paywall_share
[1] 0.84

Der Anteil an Paywall-Artikeln auf der Startseite von Le Monde ist gerade 84 Prozent.

Aufgabe

Vergleichen Sie den Anteil von Paywall-Artikeln in zwei Ressorts von Le Monde. Achtung: auf den Unterseiten sind die Teaser im Element section.teaser statt div.article untergebracht.