Web scraping with `rvest` in R

In this ExploRation, I will demonstrate how to scrape text data from the web with R. This particular example aims to collect a series of State of the Union (SOTU) speeches [1947-present] from http://www.presidency.ucsb.edu/ and write the plain-text contents to disc. The bulk of the work will be done with the recently released rvest package. The scripting will also employ the magrittr package for writing legible code.

To get started first we identify the sub-page ../sou.php that contains the links of interest.

This page contains links to pages in which all of the SOTU addresses. To load that page into R, as a parsed html object we use rvest’s read_html() function.

# Load the page
main.page <- read_html(x = "http://www.presidency.ucsb.edu/sou.php")

Once we have the page, the next step is to identify how to isolate the links that we are interested in from other links on the page. The documentation for the package refers to Selectorgadget a bookmarklet for your browser that allows you to point-and-click your way to identifying either the CSS or XPATH need to get the target html objects.

Activating Selectorgadget, you then click on the html object you want and then see what becomes highlighted. In most cases this will highlight more objects that you want, so then you click again on the object(s) you do not want to isolate. In our case, clicking first the “2013” link in the SOTU listing and then the “Florida 2000” link leaves us with the right objects selected.

Now we can return to R, and use the CSS selector ‘.ver12 a’ to get our links. The html_nodes() function gets use the elements we want, but they come html-warts and all. For the URLs we use the html_attr() function and specify that we want the part contained under href (ex. <a href="http://www.presidency.ucsb.edu/ws/index.php?pid=29431">1790</a>). The same basic process is applied to get the link text, but instead we use the html_text() function to get the ‘1790’ part of the previous URL example. Then we combine the results into a data.frame sotu.

# Get link URLs
urls <- main.page %>% # feed `main.page` to the next step
  html_nodes(".ver12 a") %>% # get the CSS nodes
  html_attr("href") # extract the URLs
# Get link text
links <- main.page %>% # feed `main.page` to the next step
  html_nodes(".ver12 a") %>% # get the CSS nodes
  html_text() # extract the link text
# Combine `links` and `urls` into a data.frame
sotu <- data.frame(links = links, urls = urls, stringsAsFactors = FALSE)

The results look great. We still need to extract only those addresses we are interested in, dates between 1947-2015. To do this we simply use the %in% operator to filter our sotu$links column by the vector 1947:2015.

sotu <- subset(x = sotu, links %in% 1947:2015) # Truman to Obama

The next step is to follow each these links, extract the text, and write the text to disc. To keep our files organized, we are going to dynamically generate the file names marking them as either republican or democrat by using the dates that Republicans held the presidency and then append the date. This will result in files with the format: republican-2001.txt.

First the filter: dates which Republicans were in office.

# Vector to mark SOTU address political party
republicans <- c(1954:1960, 1970:1974, 1974:1977, 1981:1988, 1989:1992, 2001:2008)

Now the aim is to loop through each of the links in our sotu data.frame (i.e. the number of rows nrow(sotu)), grabbing the parsed html (read_html()) and isolating (".displaytext") and extracting the relevant text (html_text). After the text has been scraped then we decide if the text should be marked Republican or Democrat using the previous filter and an ifelse() statement, compile the file name, and write that file to disc.

# Loop over each row in `sotu`
for(i in seq(nrow(sotu))) {
  text <- read_html(sotu$urls[i]) %>% # load the page
    html_nodes(".displaytext") %>% # isloate the text
    html_text() # get the text
  # Find the political party of this link
  party <- ifelse(test = sotu$links[i] %in% republicans,
                  yes = "republican", no = "democrat")
  # Create the file name
  filename <- paste0("data/SOTU/", party, "-", sotu$links[i], ".txt")
  sink(file = filename) %>% # open file to write 
  cat(text)  # write the file
  sink() # close the file

And that should do it. Looking at our directory we see that the files are now there and in order.

# View the `data/SOTU/` directory
dir(path = "data/SOTU/", full.names = TRUE)
##  [1] "data/SOTU//democrat-1947.txt"   "data/SOTU//democrat-1948.txt"  
##  [3] "data/SOTU//democrat-1949.txt"   "data/SOTU//democrat-1950.txt"  
##  [5] "data/SOTU//democrat-1951.txt"   "data/SOTU//democrat-1952.txt"  
##  [7] "data/SOTU//democrat-1953.txt"   "data/SOTU//democrat-1961.txt"  
##  [9] "data/SOTU//democrat-1962.txt"   "data/SOTU//democrat-1963.txt"  
## [11] "data/SOTU//democrat-1964.txt"   "data/SOTU//democrat-1965.txt"  
## [13] "data/SOTU//democrat-1966.txt"   "data/SOTU//democrat-1967.txt"  
## [15] "data/SOTU//democrat-1968.txt"   "data/SOTU//democrat-1969.txt"  
## [17] "data/SOTU//democrat-1978.txt"   "data/SOTU//democrat-1979.txt"  
## [19] "data/SOTU//democrat-1980.txt"   "data/SOTU//democrat-1993.txt"  
## [21] "data/SOTU//democrat-1994.txt"   "data/SOTU//democrat-1995.txt"  
## [23] "data/SOTU//democrat-1996.txt"   "data/SOTU//democrat-1997.txt"  
## [25] "data/SOTU//democrat-1998.txt"   "data/SOTU//democrat-1999.txt"  
## [27] "data/SOTU//democrat-2000.txt"   "data/SOTU//democrat-2009.txt"  
## [29] "data/SOTU//democrat-2010.txt"   "data/SOTU//democrat-2011.txt"  
## [31] "data/SOTU//democrat-2012.txt"   "data/SOTU//democrat-2013.txt"  
## [33] "data/SOTU//democrat-2014.txt"   "data/SOTU//democrat-2015.txt"  
## [35] "data/SOTU//republican-1954.txt" "data/SOTU//republican-1955.txt"
## [37] "data/SOTU//republican-1956.txt" "data/SOTU//republican-1957.txt"
## [39] "data/SOTU//republican-1958.txt" "data/SOTU//republican-1959.txt"
## [41] "data/SOTU//republican-1960.txt" "data/SOTU//republican-1970.txt"
## [43] "data/SOTU//republican-1971.txt" "data/SOTU//republican-1972.txt"
## [45] "data/SOTU//republican-1974.txt" "data/SOTU//republican-1975.txt"
## [47] "data/SOTU//republican-1976.txt" "data/SOTU//republican-1977.txt"
## [49] "data/SOTU//republican-1981.txt" "data/SOTU//republican-1982.txt"
## [51] "data/SOTU//republican-1983.txt" "data/SOTU//republican-1984.txt"
## [53] "data/SOTU//republican-1985.txt" "data/SOTU//republican-1986.txt"
## [55] "data/SOTU//republican-1987.txt" "data/SOTU//republican-1988.txt"
## [57] "data/SOTU//republican-1989.txt" "data/SOTU//republican-1990.txt"
## [59] "data/SOTU//republican-1991.txt" "data/SOTU//republican-1992.txt"
## [61] "data/SOTU//republican-2001.txt" "data/SOTU//republican-2002.txt"
## [63] "data/SOTU//republican-2003.txt" "data/SOTU//republican-2004.txt"
## [65] "data/SOTU//republican-2005.txt" "data/SOTU//republican-2006.txt"
## [67] "data/SOTU//republican-2007.txt" "data/SOTU//republican-2008.txt"

A note is in order on isolating the text on each SOTU page. Selectorgadget is really handy, but in my experience it isn’t fool proof. If you cannot get the highlighting to work, you will need to open up the html page source and do some sleuthing. In Safari on OSX, you will need to enable “Show Develop in menu bar” and then you can choose “Show Web Inspector”. Perusing the html structure you need to use some trial and error to find the CSS selector(s) that work. After some poking around, .displaytext turns out to do the trick.

comments powered by Disqus