Tweets der Mitglieder des Europaparlaments - Teil 1

Ziel

  • Twitter-Accounts der MEPs auf der Webseite des Europaparlaments abgreifen
  • Die letzten 3200 Tweets jedes Abgeordneten herunterladen
  • Daten aufbereiten und visualisieren

Wir nutzen dazu die Programmiersprache R und die Paket rvest, und die tidyverse-Paketsammlung und später rtweet. Außerdem nutzen wir die Möglichkeiten des funktionalen programmierens und erstellen eigene Funktionen. Das Tutorial richtet sich an Personen, welche bereits erste Erfahrungen mit R sammeln durften.

Datengewinnung

Auf der Webseite des Europaparlaments finden sich umfassende Angaben über alle Abgeordneten: Wir finden Informationen über Mitgliedschaften in Ausschüssen, die letzten Aktivitäten aber eben auch Verweise auf Social-Media-Auftritte und die individuellen Webseiten des*r Abgeordneten.

Seite der EFDD Abgeordneten Isabella Adinolfi

Seite der EFDD Abgeordneten Isabella Adinolfi

Auf der Übersichtsseite finden wiederum die Verweise auf die Webseiten aller 751 MEPs. Mithilfe des SelectorGadget Addons für Chrome können wir nun sehr einfach die Daten auswählen, welche wir mit rvest abgreifen wollen. Wir nutzen hier die CSS-Attribute, um beispielsweise die Namen, Fraktionszugehörigkeit und das Entsendeland zu identifizieren.

SelectorGadget mit ausgewählten CSS-Attributen

SelectorGadget mit ausgewählten CSS-Attributen

Falls rvest noch nicht installiert sein sollte, kann dies mit install_packages("rvest") erledigt werden.

library(rvest)
library(tidyverse)

mep_page <- read_html("http://www.europarl.europa.eu/meps/en/full-list/all")

mep_name <- mep_page %>%
  html_nodes(".member-name") %>%
  html_text()

mep_name %>% head(10)
##  [1] "Asim ADEMOV"                   "Isabella ADINOLFI"            
##  [3] "Marco AFFRONTE"                "Laura AGEA"                   
##  [5] "John Stuart AGNEW"             "Clara Eugenia AGUILERA GARCÍA"
##  [7] "Daniela AIUTO"                 "Tim AKER"                     
##  [9] "Marina ALBIOL GUZMÁN"          "Nedzhmi ALI"

Wir können Bestandteile der HTML-Seite mit hmtl_nodes() extrahieren und mit html_text() in einen Character-String umwandeln. Mit ein paar Zeilen mehr lassen sich auch die Links auf die individuellen Seiten der Abgeordneten abgreifen, allerdings ist dieser Code etwas komplexer.

mep_link <- mep_page %>%
  html_nodes(".single-member-container a") %>%
  html_attr("href") %>%
  paste0("http://www.europarl.europa.eu",.)

mep_link %>% head(10)
##  [1] "http://www.europarl.europa.eu/meps/en/189525"
##  [2] "http://www.europarl.europa.eu/meps/en/124831"
##  [3] "http://www.europarl.europa.eu/meps/en/124797"
##  [4] "http://www.europarl.europa.eu/meps/en/124811"
##  [5] "http://www.europarl.europa.eu/meps/en/96897" 
##  [6] "http://www.europarl.europa.eu/meps/en/125045"
##  [7] "http://www.europarl.europa.eu/meps/en/124842"
##  [8] "http://www.europarl.europa.eu/meps/en/99650" 
##  [9] "http://www.europarl.europa.eu/meps/en/125048"
## [10] "http://www.europarl.europa.eu/meps/en/34250"

Mit html_nodes(".single-member-container a") greifen wir auf alle Knoten nach single-member-container und a zu. Anschließend extrahieren wir den Wert des Attributes href in a zurück und fügen die URL http://www.europaparl.europa.eu/ mit der Funktion paste0 hinzu.

<li class="ep_item europarl-expandable-item single-member-container"> #single-member-container
            <div>
                <a class="ep_content" href="/meps/en/28292" title="Inés AYALA SENDER"> #a
                    <div class="ep-a_heading">
                        <div class="ep_title">
                            <div class="ep-p_text">
                                <span class="ep_name member-name">Inés AYALA SENDER</span><span class="ep_icon">&nbsp;</span>
                            </div>
                        </div>

Wir erstellen uns nun eine Funktion, mit welcher wir alle relevanten Informationen abgreifen können. Aus den URLs können wir die IDs mit sogenannten Regular Expressions extrahieren. Dafür nutzen wir die Funktion str_extract("\\d+$") welche die letzte Nummernfolge ("\\d+") am Ende eines Character-Strings ("$") übernimmt.

get_mep_overview <- function(x){
  data_frame(
    mep_name = x %>% html_nodes(".member-name") %>% html_text(),
    mep_link = x %>% html_nodes(".single-member-container a") %>%
      html_attr("href") %>%
      paste0("http://www.europarl.europa.eu",.),
    mep_group = x %>% html_nodes(".ep-layout_group .ep_name") %>% html_text(),
    mep_party = x %>% html_nodes(".ep-layout_party .ep_name") %>% html_text(),
    mep_country = x %>% html_nodes(".ep-layout_country .ep_name") %>% html_text(),
    mep_id = mep_link %>% str_extract("\\d+$")
    )
}

mep_df <- get_mep_overview(mep_page)

mep_df
## # A tibble: 751 x 6
##    mep_name    mep_link     mep_group       mep_party    mep_country mep_id
##    <chr>       <chr>        <chr>           <chr>        <chr>       <chr> 
##  1 Asim ADEMOV http://www.… Group of the E… Citizens fo… Bulgaria    189525
##  2 Isabella A… http://www.… Europe of Free… Movimento 5… Italy       124831
##  3 Marco AFFR… http://www.… Group of the G… Independent  Italy       124797
##  4 Laura AGEA  http://www.… Europe of Free… Movimento 5… Italy       124811
##  5 John Stuar… http://www.… Europe of Free… United King… United Kin… 96897 
##  6 Clara Euge… http://www.… Group of the P… Partido Soc… Spain       125045
##  7 Daniela AI… http://www.… Europe of Free… Movimento 5… Italy       124842
##  8 Tim AKER    http://www.… Europe of Free… Thurrock In… United Kin… 99650 
##  9 Marina ALB… http://www.… Confederal Gro… Izquierda U… Spain       125048
## 10 Nedzhmi ALI http://www.… Group of the A… Movement fo… Bulgaria    34250 
## # ... with 741 more rows

Daten auf den individuellen Seiten der MEPs

Von den individuellen Seiten können wir uns nun die weiteren Daten herunterladen. Wir bleiben beim Beispiel von Isabella Adinolfi:

indiv_page <- read_html("http://www.europarl.europa.eu/meps/en/124831/ISABELLA_ADINOLFI/home")

indiv_page %>%
  html_nodes(".link_twitt a") %>%
  html_attr("href")
## [1] "http://twitter.com/Isa_Adinolfi"

Da mehr als nur die Twitter-Links von jeder Seite heruntergeladen werden sollen, schreiben wir eine Funktion mit welcher wir die Daten der Abgeordneten abgreifen können.

get_mep_data <- function(x){
  page <- read_html(x)

  data_frame(
    mep_id = x %>% str_extract("\\d+$"),
    website = page %>% html_nodes(".link_website") %>% html_attr("href") %>% list(),
    email = page %>% html_nodes(".link_email a") %>% html_attr("href") %>% list(),
    twitter = page %>% html_nodes(".link_twitt a") %>% html_attr("href") %>% list(),
    facebook = page %>% html_nodes(".link_fb a") %>% html_attr("href") %>% list(),
    youtube = page %>% html_nodes(".link_youtube a") %>% html_attr("href") %>% list(),
    linkedin = page %>% html_nodes(".link_linkedin a") %>% html_attr("href") %>% list(),
    instagram = page %>% html_nodes(".link_instagram a") %>% html_attr("href") %>% list(),
    mep_birthday = page %>% html_nodes("#birthDate") %>% html_text() %>% list()
  )
}

Wir speichern die einzelnen Variablen in list(), da manche Abgeordnete keine oder sogar mehrere Facebookprofile angegeben haben. Wir können mittels des Packets purrr nun die Funktoiin get_mep_data() auf alle Links anwenden und somit die Daten aller 751 Abgeordneten abgreifen. Allerdings benötigt das auch seine Zeit, deshalb beschleunigen wir das Vorgehen indem wir das Multiprozessfähige furrr-Paket verwenden.

library(furrr)
plan(multiprocess)

mep_social_media <- future_map_dfr(mep_df$mep_link, get_mep_data)

mep_social_media
## # A tibble: 751 x 9
##    mep_id website email twitter facebook youtube linkedin instagram
##    <chr>  <list>  <lis> <list>  <list>   <list>  <list>   <list>   
##  1 189525 <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
##  2 124831 <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
##  3 124797 <chr [… <chr… <chr [… <chr [0… <chr [… <chr [0… <chr [0]>
##  4 124811 <chr [… <chr… <chr [… <chr [0… <chr [… <chr [0… <chr [0]>
##  5 96897  <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
##  6 125045 <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
##  7 124842 <chr [… <chr… <chr [… <chr [0… <chr [… <chr [0… <chr [0]>
##  8 99650  <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
##  9 125048 <chr [… <chr… <chr [… <chr [2… <chr [… <chr [0… <chr [0]>
## 10 34250  <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
## # ... with 741 more rows, and 1 more variable: mep_birthday <list>

Mit unnest() können wir die Inhalte der Listen anschließend genauer untersuchen. Da sowohl mep_social_media als auch mep_df mit den IDs der Abgeordneten versehen sind, können beide Datenframes auch miteinander verknüpft werden. Somit können wir herausfinden, welche*r Abgeordnete welchen Account angegeben hat.

mep_df %>%
  left_join(mep_social_media, by = "mep_id") %>%
  unnest(twitter) %>%
  select(mep_name, twitter)
## # A tibble: 519 x 2
##    mep_name                      twitter                             
##    <chr>                         <chr>                               
##  1 Asim ADEMOV                   https://twitter.com/ademovasim      
##  2 Isabella ADINOLFI             http://twitter.com/Isa_Adinolfi     
##  3 Clara Eugenia AGUILERA GARCÍA https://twitter.com/ClaraAguilera7  
##  4 Tim AKER                      http:// https://twitter.com/Tim_Aker
##  5 Marina ALBIOL GUZMÁN          https://twitter.com/MarinaAlbiol    
##  6 Michèle ALLIOT-MARIE          https://twitter.com/malliotmarie    
##  7 Lucy ANDERSON                 https://twitter.com/LucyAndersonMEP 
##  8 Martina ANDERSON              https://twitter.com/MEPStandingUp4U 
##  9 Max ANDERSSON                 https://twitter.com/MaxAndersson    
## 10 Eric ANDRIEU                  https://twitter.com/Eric_Andrieu    
## # ... with 509 more rows

Daten aufbereiten

Leider waren nicht alle Abgeordneten beim einpflegen der Daten besonders sauber. Wie man bereits an Tim Aker erkennen kann, würde dessen Link nicht funktionieren (http:// https://twitter.com/Tim_Aker). Außerdem geben manche Abgeordneten auch nicht ihren eigenen Account, sondern den Fraktionsaccount an. Wir müssen deshalb die Daten aufbereiten und nur die Twitter-Accountnamen extrahieren und die Fraktionsaccounts filtern.

Dafür nutzen wir abermals regular expressions.

mep_twitter <- mep_social_media %>%
  unnest(twitter) %>%
  select(mep_id, twitter) %>%
  mutate(screen_name = str_remove(twitter, "\\?lang=[a-z]{2}")) %>% # removes the "?lang=es" at the end of the link
  mutate(screen_name = str_remove(screen_name, "\\/media")) %>% # removes the media at the end of some links
  mutate(screen_name = str_remove(screen_name, "\\/$")) %>% # removes slashes at the end
  mutate(screen_name = str_extract(screen_name, "\\w+$")) %>% # extracts the last word before the end of the string
  mutate(screen_name = str_to_lower(screen_name))

Und das filtern der Fraktionsaccounts:

ep_group_accounts <- c("ppegrupo", "eppgroup", "theprogressives", "aldegroup", "guengl", "enf_ep", "efdgroup", "ecrgroup")

mep_twitter <- mep_twitter %>%
  filter(!screen_name %in% ep_group_accounts) %>%
  distinct(screen_name, .keep_all = TRUE)

mep_twitter
## # A tibble: 502 x 3
##    mep_id twitter                              screen_name    
##    <chr>  <chr>                                <chr>          
##  1 189525 https://twitter.com/ademovasim       ademovasim     
##  2 124831 http://twitter.com/Isa_Adinolfi      isa_adinolfi   
##  3 125045 https://twitter.com/ClaraAguilera7   claraaguilera7 
##  4 99650  http:// https://twitter.com/Tim_Aker tim_aker       
##  5 125048 https://twitter.com/MarinaAlbiol     marinaalbiol   
##  6 1179   https://twitter.com/malliotmarie     malliotmarie   
##  7 124949 https://twitter.com/LucyAndersonMEP  lucyandersonmep
##  8 113959 https://twitter.com/MEPStandingUp4U  mepstandingup4u
##  9 124994 https://twitter.com/MaxAndersson     maxandersson   
## 10 113892 https://twitter.com/Eric_Andrieu     eric_andrieu   
## # ... with 492 more rows

Es bleiben uns 502 Accounts, mit denen wir weiter arbeiten können. Wir speichern sie noch ab:

mep_final <- mep_df %>%
  left_join(mep_twitter, by = "mep_id")
write_rds(mep_final, "mep_final.RDS")

Wir konnten also mit Web-Scraping erfolgreich alle Twitteraccounts der MEPs abgreifen – zumindest all jener MEPs, welche ihren Twitteraccount auch prominent auf der Seite des Parlaments angegeben haben. Im nächsten Schritt können wir mit dem Paket rtweet die Tweets der jeweiligen Accounts herunterladen und visualisieren.

Der komplette Code findet sich auch auf Github.

Avatar
Josef Holnburger
Politischer Referent und BA Politikwissenschaft