Extracting media text from newspaper’s websites is a very frequent task in webscraping. One advantage of these sites is that they tend to offer an RSS feed that contains a list of all the stories they have published, which we can then use to more efficiently scrape them.

Parsing RSS feeds requires we learn a slightly different data format: XML, or eXtensible Markup Language, which predates (but is similar to) JSON. Just like HTML, it uses a series of tags and a tree structure. We will use the xml2 and rvest packages to read data in XML format:

Let’s look at an example:

feed <- "http://www.spiegel.de/politik/index.rss"
library(xml2)
library(rvest)
rss <- read_xml(feed)
substr(as.character(rss), 1, 1000)
## [1] "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<rss xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" version=\"2.0\">\n  <channel>\n    <title>SPIEGEL ONLINE - Politik</title>\n    <link>http://www.spiegel.de</link>\n    <description>Politik-Nachrichten aus Deutschland und den Brennpunkten in aller Welt. Aktuelle Reportagen, Analysen, Interviews.</description>\n    <language>de</language>\n    <pubDate>Tue, 01 Aug 2017 12:14:17 +0200</pubDate>\n    <lastBuildDate>Tue, 01 Aug 2017 12:14:17 +0200</lastBuildDate>\n    <image>\n      <title>SPIEGEL ONLINE</title>\n      <link>http://www.spiegel.de</link>\n      <url>http://www.spiegel.de/static/sys/logo_120x61.gif</url>\n    </image>\n    <item>\n      <title>\"Du weißt, was Du getan hast\": Trump-Berater fallen auf Scherz-E-Mail herein</title>\n      <link>http://www.spiegel.de/politik/ausland/donald-trump-maechtige-berater-fallen-laut-cnn-auf-e-mail-betrueger-herein-a-1160841.html#ref=rss</link>\n      <description>Ein Betrüger gab sich in "

Just like with HTML, we can extract specific nodes of the XML file using a combination of xml_nodes and xml_text

headlines <- xml_nodes(rss, 'title')
(headlines <- xml_text(headlines))
##  [1] "SPIEGEL ONLINE - Politik"                                                          
##  [2] "SPIEGEL ONLINE"                                                                    
##  [3] "\"Du weißt, was Du getan hast\": Trump-Berater fallen auf Scherz-E-Mail herein"    
##  [4] "Dieselkrise und Wahlkampf: Voll verfahren"                                         
##  [5] "Ehefrau von Oppositionsführer in Venezuela: \"Sie haben gerade Leopoldo abgeholt\""
##  [6] "Russlandaffäre: Vater Trump soll Sohn Trump irreführende Aussage diktiert haben"   
##  [7] "Messerattentat in Hamburg: Linken-Politiker für mehr Psychologen"                  
##  [8] "Reaktionen auf Scaramucci: \"Kürzeste Rundfahrt in der politischen Geschichte\""   
##  [9] "Newsblog: Das war Der Morgen @SPIEGELONLINE am 01.08.2017"                         
## [10] "Chaos im Weißen Haus: Feuer frei "                                                 
## [11] "Die Lage am Dienstag: Merkels Manko"                                               
## [12] "Neuseeland: Oppositionschef Little tritt kurz vor Wahl zurück"                     
## [13] "Reaktion auf US-Sanktionen: Maduro spottet über \"Imperator\" Trump"               
## [14] "Venezuela: USA verhängen Sanktionen gegen Maduro"                                  
## [15] "Weißes Haus: Trump feuert Scaramucci - nach nur zehn Tagen"                        
## [16] "Trumps neuer Stabschef: Der General und seine unmögliche Mission"                  
## [17] "Ausreisepflichtiger Attentäter Ahmad A.: Woran Abschiebungen scheitern"            
## [18] "Streit mit der Türkei: Bundeswehr zieht letzten \"Tornado\" aus Incirlik ab"       
## [19] "Somalia: Zwölf Soldaten sterben bei Terrorangriff"                                 
## [20] "Trump über neuen Stabschef Kelly: \"Er wird einen spektakulären Job machen\""      
## [21] "Drohung gegen US-Vertretungen: Putins Attacke schadet vor allem Russen"            
## [22] "Messerangriff in Hamburg: Bundesanwaltschaft übernimmt Ermittlungen"
urls <- xml_nodes(rss, 'link')
(urls <- xml_text(urls))
##  [1] "http://www.spiegel.de"                                                                                                                             
##  [2] "http://www.spiegel.de"                                                                                                                             
##  [3] "http://www.spiegel.de/politik/ausland/donald-trump-maechtige-berater-fallen-laut-cnn-auf-e-mail-betrueger-herein-a-1160841.html#ref=rss"           
##  [4] "http://www.spiegel.de/politik/deutschland/dieselgipfel-und-wahlkampf-voll-verfahren-a-1160747.html#ref=rss"                                        
##  [5] "http://www.spiegel.de/politik/ausland/leopoldo-lopez-venezuela-ehefrau-meldet-festnahme-durch-geheimdienst-a-1160824.html#ref=rss"                 
##  [6] "http://www.spiegel.de/politik/ausland/donald-trump-soll-sohn-donald-junior-statement-zu-russlandaffaere-diktiert-haben-a-1160818.html#ref=rss"     
##  [7] "http://www.spiegel.de/politik/deutschland/ahmad-a-und-die-messerattacke-von-hamburg-linke-fordert-mehr-psychologen-a-1160814.html#ref=rss"         
##  [8] "http://www.spiegel.de/politik/ausland/anthony-scaramucci-reaktionen-auf-die-entlassung-von-donald-trumps-kommunikationschef-a-1160813.html#ref=rss"
##  [9] "http://www.spiegel.de/politik/deutschland/nachrichten-am-morgen-die-news-in-echtzeit-a-1159974.html#ref=rss"                                       
## [10] "http://www.spiegel.de/politik/ausland/donald-trump-entlaesst-anthony-scaramucci-analyse-zum-chaos-im-weissen-haus-a-1160804.html#ref=rss"          
## [11] "http://www.spiegel.de/politik/deutschland/news-wahlkampf-in-deutschland-donald-trump-alexander-dobrindt-apple-zahlen-a-1160802.html#ref=rss"       
## [12] "http://www.spiegel.de/politik/ausland/neuseeland-andrew-little-tritt-als-oppositionschef-zurueck-jacinda-ardern-uebernimmt-a-1160806.html#ref=rss" 
## [13] "http://www.spiegel.de/politik/ausland/venezuela-nicolas-maduro-spottet-ueber-donald-trump-und-us-sanktionen-a-1160805.html#ref=rss"                
## [14] "http://www.spiegel.de/politik/ausland/venezuela-usa-verhaengen-sanktionen-gegen-praesident-nicolas-maduro-a-1160796.html#ref=rss"                  
## [15] "http://www.spiegel.de/politik/ausland/donald-trump-feuert-anthony-scaramucci-laut-nyt-nach-nur-zehn-tagen-a-1160791.html#ref=rss"                  
## [16] "http://www.spiegel.de/politik/ausland/donald-trump-und-sein-stabschef-john-kelly-general-auf-unmoeglicher-mission-a-1160787.html#ref=rss"          
## [17] "http://www.spiegel.de/politik/deutschland/ahmad-a-und-die-messerattacke-von-hamburg-woran-scheitern-abschiebungen-a-1160756.html#ref=rss"          
## [18] "http://www.spiegel.de/politik/ausland/tuerkei-bundeswehr-zieht-letzte-tornado-jets-aus-incirlik-ab-a-1160778.html#ref=rss"                         
## [19] "http://www.spiegel.de/politik/ausland/somalia-terror-angriff-von-al-shabab-zwoelf-soldaten-der-afrikanischen-union-tot-a-1160771.html#ref=rss"     
## [20] "http://www.spiegel.de/politik/ausland/usa-donald-trump-vereidigt-john-kelly-als-stabschef-a-1160754.html#ref=rss"                                  
## [21] "http://www.spiegel.de/politik/ausland/waldimir-putins-reaktion-auf-us-sanktionen-trifft-vor-allem-russen-a-1160720.html#ref=rss"                   
## [22] "http://www.spiegel.de/politik/deutschland/hamburg-bundesanwaltschaft-uebernimmt-ermittlungen-zu-messerangriff-a-1160757.html#ref=rss"

Once we have the article URLs, we could go page by page, looking at their internal structure, and then scraping it. However, some packages exist that already compile a set of scrapers that generally work with any type of newspaper website – one of these is boilerpipeR. It uses a combination of machine learning and heuristics to develop functions that should work for any newspaper website. Let’s see how it works in this case:

library(boilerpipeR)
# read first URL -- note that all text needs to be into a single character vector
text <- readLines(urls[3])
## Warning in readLines(urls[3]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/donald-trump-maechtige-berater-fallen-laut-
## cnn-auf-e-mail-betrueger-herein-a-1160841.html#ref=rss'
text <- paste(text, collapse="\n")
# now let's try to parse it..
main_text <- ArticleExtractor(text)
cat(main_text)
## Feedback
## Mehrere hochrangige Mitarbeiter des Weißen Hauses sollen auf einen E-Mail-Betrüger hereingefallen sein, meldet CNN . Der Unbekannte, der sich auf Twitter E-Mail Prankster nennt, gab vor, zum engen Zirkel von US-Präsident Donald Trump zu gehören.
## Der zurückgetretene Kommunikationschef des Weißen Hauses, Anthony Scaramucci, etwa ließ sich dem Bericht zufolge von dem Betrüger täuschen, der eine Mail an den Ex-Kommunikationschef mit "Reince Priebus" unterzeichnete - mittlerweile Ex-Stabschef von Trump.
## Liest man den Mail-Verkehr, den CNN fast ungekürzt veröffentlicht, zeigt sich, wie geschickt der falsche Priebus die offenbar reale Fehde zwischen Priebus und Scaramucci für seine Scherz-E-Mails zu nutzen wusste.
## Der falsche Ex-Stabschef schrieb an den echten Scaramucci: "Ich hatte mir geschworen, mir die Hände nicht schmutzig zu machen, aber nachdem ich Deinen Tweet las, in dem es hieß 'Man werde ja sehen, wer unter den Medien Klasse hat und wer nicht', konnte ich nicht anders. Der Tweet war auf atemberaubende Weise verlogen, selbst für dich. Zu keinem Zeitpunkt hast Du Dich so verhalten, als hättest Du entfernt so etwas wie Klasse." Kelly werde den Job besser machen als er selbst, schrieb der falsche Priebus. Und: "Ich erwarte keine Antwort."
## Der echte Scaramucci antwortete darauf: "Du weißt, was Du getan hast. Wir alle wissen es. Selbst heute. Aber sei Dir sicher, wir waren vorbereitet. Ein Mann würde sich entschuldigen." Der falsche Priebus schoss zurück, es gebe nichts, wofür er sich entschuldigen müsse. Und Scaramucci darauf: "Lies Shakespeare. Besonders Othello. Da bist Du richtig. Meiner Familie geht es übrigens gut, sie wird sich gut entwickeln. Ich weiß was Du getan hast. Keine Antworten mehr von mir."
## In einer E-Mail an US-Heimatschutzberater Tom Bossert soll der Schwindler sich als Trumps Schwiegersohn Jared Kushner ausgegeben haben. "Tom, wir veranstalten Ende August eine Art Soirée", schrieb der falsche Kushner in einer über einen Outlook-Account gesendeten Mail an Bossert. "Es wäre großartig, wenn Du vorbeikommen könntest, ich verspreche Essen von mindestens vergleichbarer Qualität zu dem, was wir im Irak zu uns genommen haben. Es sollte ein großartiger Abend werden."
## Internetexperte Bossert: "Danke, Jared. Hier meine private E-Mail-Adresse"
## Bossert, der im Weißen Haus unter anderem für das Thema Internetsicherheit zuständig ist, antwortete: "Danke, Jared. Bei einem solchen Versprechen kann ich nicht Nein sagen. Und, falls Du sie einmal brauchen solltest, hier meine private E-Mail-Adresse [...]."
## Das Weiße Haus bestätigte CNN die Vorfälle. "Wir nehmen alle Themen im Zusammenhang mit dem Internet sehr ernst", sagte Trumps Sprecherin Sarah Sanders dem Sender.
## cht/dpa

Once we have prototype code, the last step is to generalize using a loop that will iterate over URLs.

articles <- list()
for (i in 1:length(urls)){

    message(i, " of ", length(urls))
    text <- paste(readLines(urls[i]), collapse="\n")
    main_text <- ArticleExtractor(text)
    articles[[i]] <- data.frame(
        url = urls[i],
        headline = headlines[i],
        text = main_text,
        stringsAsFactors=F)

}
## 1 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de'
## 2 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de'
## 3 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/donald-trump-maechtige-berater-fallen-laut-
## cnn-auf-e-mail-betrueger-herein-a-1160841.html#ref=rss'
## 4 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/deutschland/dieselgipfel-und-wahlkampf-voll-
## verfahren-a-1160747.html#ref=rss'
## 5 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/leopoldo-lopez-venezuela-ehefrau-meldet-
## festnahme-durch-geheimdienst-a-1160824.html#ref=rss'
## 6 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/donald-trump-soll-sohn-donald-junior-
## statement-zu-russlandaffaere-diktiert-haben-a-1160818.html#ref=rss'
## 7 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/deutschland/ahmad-a-und-die-messerattacke-von-
## hamburg-linke-fordert-mehr-psychologen-a-1160814.html#ref=rss'
## 8 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/anthony-scaramucci-reaktionen-auf-die-
## entlassung-von-donald-trumps-kommunikationschef-a-1160813.html#ref=rss'
## 9 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/deutschland/nachrichten-am-morgen-die-news-in-
## echtzeit-a-1159974.html#ref=rss'
## 10 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/donald-trump-entlaesst-anthony-scaramucci-
## analyse-zum-chaos-im-weissen-haus-a-1160804.html#ref=rss'
## 11 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/deutschland/news-wahlkampf-in-deutschland-donald-
## trump-alexander-dobrindt-apple-zahlen-a-1160802.html#ref=rss'
## 12 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/neuseeland-andrew-little-tritt-als-
## oppositionschef-zurueck-jacinda-ardern-uebernimmt-a-1160806.html#ref=rss'
## 13 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/venezuela-nicolas-maduro-spottet-ueber-
## donald-trump-und-us-sanktionen-a-1160805.html#ref=rss'
## 14 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/venezuela-usa-verhaengen-sanktionen-gegen-
## praesident-nicolas-maduro-a-1160796.html#ref=rss'
## 15 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/donald-trump-feuert-anthony-scaramucci-laut-
## nyt-nach-nur-zehn-tagen-a-1160791.html#ref=rss'
## 16 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/donald-trump-und-sein-stabschef-john-kelly-
## general-auf-unmoeglicher-mission-a-1160787.html#ref=rss'
## 17 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/deutschland/ahmad-a-und-die-messerattacke-von-
## hamburg-woran-scheitern-abschiebungen-a-1160756.html#ref=rss'
## 18 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/tuerkei-bundeswehr-zieht-letzte-tornado-
## jets-aus-incirlik-ab-a-1160778.html#ref=rss'
## 19 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/somalia-terror-angriff-von-al-shabab-zwoelf-
## soldaten-der-afrikanischen-union-tot-a-1160771.html#ref=rss'
## 20 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/usa-donald-trump-vereidigt-john-kelly-als-
## stabschef-a-1160754.html#ref=rss'
## 21 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/ausland/waldimir-putins-reaktion-auf-us-sanktionen-
## trifft-vor-allem-russen-a-1160720.html#ref=rss'
## 22 of 22
## Warning in readLines(urls[i]): incomplete final line found on 'http://
## www.spiegel.de/politik/deutschland/hamburg-bundesanwaltschaft-uebernimmt-
## ermittlungen-zu-messerangriff-a-1160757.html#ref=rss'
articles <- do.call(rbind, articles)

Of course, some times this standardized code will not work with specific websites. In those cases, it’s easier to just develop our own code. Here I show you an example written by one of the students in my lab, Anthony Ramos.

The following two blocks of code to the following: 1) download the home page of the Spiegel in html format, 2) extract the URLs of all articles linked in the home page, and then download those, and finally 3) parse the html code in those pages.

scrapeSpiegelOnline <- function(path) {

  html <- download.file("http://www.spiegel.de/",
    destfile=path)
  doc <-read_html(path)

  #get main articles
  title=html_nodes(doc,".article-title a")
  titles = xml_attr(title,"title")
  title_links = xml_attr(title,"href")

  title_links <- ifelse( grepl("https?://", title_links),
    title_links, paste0("http://www.spiegel.de", title_links) )

  df <- data.frame(title=titles, url=title_links, 
                   time=as.character(Sys.time()), stringsAsFactors=F)
  df <- df[!is.na(df$url),]
  df <- df[!duplicated(df$url),]
  
  return(df)
}


scrapeSpiegelArticle <- function(url, path) {
  
  html <- download.file(url, destfile=path, quiet=TRUE)

  article <- read_html(path)
  article_intro <- html_text(html_nodes(article,".headline-intro"))[1]
  article_title <- html_text(html_nodes(article,".headline"))[1]
  article_title <- paste(article_intro,article_title,sep=": ")
  
  date <- html_text(html_nodes(article,".article-function-date"))[1]
  date <- gsub("\t|\n|\r", "", gsub("\r", "", date))
  
  content <- html_text(html_nodes(article,"p"))
  summary <- content[1]
  
  content <- paste(content, collapse="\n\n")
  content <- gsub("^ *| *$", "", gsub("\n|\t|\r", "", content))
  
  summary <- gsub("^ *| *$", "", gsub("\n", "", summary))
  summary <- gsub(" {2,}", " ", summary)
  
  comments <- grep("insgesamt (.*) Beiträge",html_text(html_nodes(article, "span")),value=TRUE)
  comments <- gsub("^ *", "", gsub("\r|\n|\t", "", comments))
  
  
  article_df <- data.frame(url=url, date=date, summary=summary,
                           headline=article_title, text=content,
                           comments=ifelse(length(comments)==0, NA, comments),
                           stringsAsFactors=F)
                           
  return(article_df)

}
today <- Sys.Date()

# filename for homepage, in html and csv format
homepage <- paste0("home-", today, ".html")
homepagecsv <- paste0("articles-", today, ".csv")

# folder where home articles will be stored
artfolder <- paste0("home-articles")
try(dir.create(artfolder, showWarnings=FALSE))

# scraping homepage and saving .html to "homepage" file
headlines <- scrapeSpiegelOnline(path=homepage)

# scraping 10 first articles linked in the homepage
articles <- list()
for (i in 1:10){
    message(headlines$title[i])
  artname <- paste0(artfolder, "/", 
        gsub("/", "_", gsub("http://www.spiegel.de/", "", headlines$url[i])) )
    # download html file and parse it
  error <- tryCatch(
        articles[[i]] <- scrapeSpiegelArticle(
            url = headlines$url[i], path=artname),
        error=function(e) e)
    if (inherits(error, "error")){ message("Error")}
    Sys.sleep(1)
}
## Dieselkrise und Wahlkampf: Voll verfahren
## Dieselskandal: EU-Fahnder empfehlen Ermittlungen gegen Volkswagen
## SPON-Umfragen: Mehrheit hält wirtschaftliche Erfolge für unfair verteilt
## Russlandaffäre: Vater Trump soll Sohn Trump irreführende Aussage diktiert haben
## Chaos im Weißen Haus: Feuer frei
## Anthony Scaramucci: 10 Tage, 10 Zitate
## Fake News im Wahlkampf: "Das Hauptziel ist Merkel"
## Umstrittene Ernährungsregeln: Low-Fat und High-Carb - ist das wirklich gesund?
## Ehefrau von Oppositionsführer in Venezuela: "Sie haben gerade Leopoldo abgeholt"
## Rolls-Royce Phantom VIII: Komm in meine Arme
articles <- do.call(rbind, articles)

# merging
headlines <- merge(headlines, articles, by="url", all.x=TRUE, sort=FALSE)

# writing to disk
write.csv(headlines, file=homepagecsv, row.names=FALSE)