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] "Magdalena ADAMOWICZ"           "Asim ADEMOV"                  
##  [3] "Isabella ADINOLFI"             "Matteo ADINOLFI"              
##  [5] "Alex AGIUS SALIBA"             "Mazaly AGUILAR"               
##  [7] "Clara AGUILERA"                "Scott AINSLIE"                
##  [9] "Alexander ALEXANDROV YORDANOV" "François ALFONSI"

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/197490"
##  [2] "http://www.europarl.europa.eu/meps/en/189525"
##  [3] "http://www.europarl.europa.eu/meps/en/124831"
##  [4] "http://www.europarl.europa.eu/meps/en/197826"
##  [5] "http://www.europarl.europa.eu/meps/en/197403"
##  [6] "http://www.europarl.europa.eu/meps/en/198096"
##  [7] "http://www.europarl.europa.eu/meps/en/125045"
##  [8] "http://www.europarl.europa.eu/meps/en/197696"
##  [9] "http://www.europarl.europa.eu/meps/en/197836"
## [10] "http://www.europarl.europa.eu/meps/en/96750"

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: 748 x 6
##    mep_name    mep_link     mep_group       mep_party    mep_country mep_id
##    <chr>       <chr>        <chr>           <chr>        <chr>       <chr> 
##  1 Magdalena … http://www.… Group of the E… Independent  Poland      197490
##  2 Asim ADEMOV http://www.… Group of the E… Citizens fo… Bulgaria    189525
##  3 Isabella A… http://www.… Non-attached M… Movimento 5… Italy       124831
##  4 Matteo ADI… http://www.… Identity and D… Lega         Italy       197826
##  5 Alex AGIUS… http://www.… Group of the P… Partit Labu… Malta       197403
##  6 Mazaly AGU… http://www.… European Conse… VOX          Spain       198096
##  7 Clara AGUI… http://www.… Group of the P… Partido Soc… Spain       125045
##  8 Scott AINS… http://www.… Group of the G… Green Party  United Kin… 197696
##  9 Alexander … http://www.… Group of the E… Union of De… Bulgaria    197836
## 10 François A… http://www.… Group of the G… Régions et … France      96750 
## # … with 738 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] "https://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: 748 x 9
##    mep_id website email twitter facebook youtube linkedin instagram
##    <chr>  <list>  <lis> <list>  <list>   <list>  <list>   <list>   
##  1 197490 <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [1]>
##  2 189525 <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
##  3 124831 <chr [… <chr… <chr [… <chr [0… <chr [… <chr [0… <chr [0]>
##  4 197826 <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
##  5 197403 <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [0]>
##  6 198096 <chr [… <chr… <chr [… <chr [0… <chr [… <chr [1… <chr [0]>
##  7 125045 <chr [… <chr… <chr [… <chr [1… <chr [… <chr [0… <chr [1]>
##  8 197696 <chr [… <chr… <chr [… <chr [0… <chr [… <chr [0… <chr [1]>
##  9 197836 <chr [… <chr… <chr [… <chr [0… <chr [… <chr [0… <chr [0]>
## 10 96750  <chr [… <chr… <chr [… <chr [0… <chr [… <chr [0… <chr [0]>
## # … with 738 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: 436 x 2
##    mep_name            twitter                            
##    <chr>               <chr>                              
##  1 Magdalena ADAMOWICZ https://twitter.com/Adamowicz_Magda
##  2 Isabella ADINOLFI   https://twitter.com/Isa_Adinolfi   
##  3 Clara AGUILERA      https://twitter.com/ClaraAguilera7 
##  4 Scott AINSLIE       https://twitter.com/scottjainslie  
##  5 Christian ALLARD    https://twitter.com/christia_allard
##  6 Abir AL-SAHLANI     https://twitter.com/abiralsahlani  
##  7 Andris AMERIKS      https://twitter.com/AndrisAmeriks  
##  8 Martina ANDERSON    https://twitter.com/M_AndersonSF   
##  9 Rasmus ANDRESEN     https://twitter.com/RasmusAndresen 
## 10 Eric ANDRIEU        https://twitter.com/Eric_Andrieu   
## # … with 426 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: 436 x 3
##    mep_id twitter                             screen_name    
##    <chr>  <chr>                               <chr>          
##  1 197490 https://twitter.com/Adamowicz_Magda adamowicz_magda
##  2 124831 https://twitter.com/Isa_Adinolfi    isa_adinolfi   
##  3 125045 https://twitter.com/ClaraAguilera7  claraaguilera7 
##  4 197696 https://twitter.com/scottjainslie   scottjainslie  
##  5 197644 https://twitter.com/christia_allard christia_allard
##  6 197400 https://twitter.com/abiralsahlani   abiralsahlani  
##  7 197783 https://twitter.com/AndrisAmeriks   andrisameriks  
##  8 113959 https://twitter.com/M_AndersonSF    m_andersonsf   
##  9 197448 https://twitter.com/RasmusAndresen  rasmusandresen 
## 10 113892 https://twitter.com/Eric_Andrieu    eric_andrieu   
## # … with 426 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.