R: scraping dynamic links with rvest

Viewed 415

I'm trying to scrape links to RSS feeds from internet archive that sit under 'dynamic' calendar using rvest, see this link as an example.

<div>
<div class="captures">
<div class="position" style="width: 20px; height: 20px;">
<div class="measure ">
</div>
</div>
<a href="/web/20100112114601/http://www.dailyecho.co.uk/news/district/winchester/rss/">12</a>
</div>
<!-- react-empty: 2310 --></div>

For example,

url %>% 
  read_html() %>%
  html_nodes("a") %>% 
  html_attr("href")

doesn't return links I'm interested in, xpath or html_nodes('.captures') return empty results. Any hints would be very helpful, thanks!

1 Answers

One possibility is to use the wayback package (GL) (GH) which has support for querying the Internet Archive and reading in the HTML of saved pages ("mementos"). You can research a bit more abt web archiving terminology (it's a bit arcane IMO) via http://www.mementoweb.org/guide/quick-intro/ & https://mementoweb.org/guide/rfc/ as starter resources.

library(wayback) # devtools::install_git(one of the superscript'ed links above)
library(rvest) # for reading the resulting HTML contents
library(tibble) # mostly for prettier printing of data frames

There are a number of approaches one could take. This is what I tend to do during forensic analysis of online content. YMMV.

First, we get the recorded mementos (basically a short-list of relevant content):

(rss <- get_mementos("http://www.dailyecho.co.uk/news/district/winchester/rss/"))
## # A tibble: 7 x 3
##   link                                                             rel       ts                 
##   <chr>                                                            <chr>     <dttm>             
## 1 http://www.dailyecho.co.uk/news/district/winchester/rss/         original  NA                 
## 2 http://web.archive.org/web/timemap/link/http://www.dailyecho.co… timemap   NA                 
## 3 http://web.archive.org/web/http://www.dailyecho.co.uk/news/dist… timegate  NA                 
## 4 http://web.archive.org/web/20090517035444/http://www.dailyecho.… first me… 2009-05-17 03:54:44
## 5 http://web.archive.org/web/20180712045741/http://www.dailyecho.… prev mem… 2018-07-12 04:57:41
## 6 http://web.archive.org/web/20180812213013/http://www.dailyecho.… memento   2018-08-12 21:30:13
## 7 http://web.archive.org/web/20180812213013/http://www.dailyecho.… last mem… 2018-08-12 21:30:13

The calendar-menu viewer thing at IA is really the "timemap". I like to work with this as it's the point-in-time memento list of all the crawls. It's the second link above so we'll read it in:

(tm <- get_timemap(rss$link[2]))
## # A tibble: 45 x 5
##    rel           link                                  type        from          datetime       
##    <chr>         <chr>                                 <chr>       <chr>         <chr>          
##  1 original      http://www.dailyecho.co.uk:80/news/d… NA          NA            NA             
##  2 self          http://web.archive.org/web/timemap/l… applicatio… Sun, 17 May … NA             
##  3 timegate      http://web.archive.org                NA          NA            NA             
##  4 first memento http://web.archive.org/web/200905170… NA          NA            Sun, 17 May 20…
##  5 memento       http://web.archive.org/web/200908130… NA          NA            Thu, 13 Aug 20…
##  6 memento       http://web.archive.org/web/200911121… NA          NA            Thu, 12 Nov 20…
##  7 memento       http://web.archive.org/web/201001121… NA          NA            Tue, 12 Jan 20…
##  8 memento       http://web.archive.org/web/201007121… NA          NA            Mon, 12 Jul 20…
##  9 memento       http://web.archive.org/web/201011271… NA          NA            Sat, 27 Nov 20…
## 10 memento       http://web.archive.org/web/201106290… NA          NA            Wed, 29 Jun 20…
## # ... with 35 more rows

The content is in the mementos and there should be as many mementos there as you see in the calendar view. We'll read in the first one:

mem <- read_memento(tm$link)
# Ideally use writeLines(), now, to save this to disk with a good
# filename. Alternatively, stick it in a data frame with metadata 
# and saveRDS() it. But, that's not a format others (outside R) can 
# use so perhaps do the data frame thing and stream it out as ndjson
# with jsonlite::stream_out() and compress it during save or afterwards.

Then convert it to something we can use programmatically with xml2::read_xml() or xml2::read_html() (RSS is sometimes better parsed as XML):

read_html(mem)
## {xml_document}
## <html>
## [1] <body><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Daily Ec ...

read_memento() has an as parameter to automagically parse the result but I like to store the mementos locally (as noted in the comments) so as not to abuse the IA servers (i.e. if I ever need to get the data again I don't have to hit their infrastructure).

A big caveat is that if you try to get too many resources from the IA in a short period of time you'll get temporarily banned as they have scale but it's a free service and they (rightfully) try to prevent abuse.

Definitely file issues to the package (pick your favourite source code hosting community to do so as I'll work with either but prefer GitLab after the Microsoft takeover of GitHub) if anything is unclear or you feel could be made better. It's not a popular package and I only have occasional need for forensic spelunking so it "works for me" but I'll gladly try to make it more user-friendly (I just need to know the pain points).

Related