Web Scraping

Scraping in the Presence of Javascript

I recently wanted to scrape a Javascript-heavy Web site. Good Web sites provide a meaningful structure in their HTML, which we can fetch with curl or wget and query with tools like xidel. Unfortunately many crappy sites will have poor page structure, and generate a lot of their content after the initial request by using Javascript.

There are tools like PhantomJS which can be used to help scrape such sites, but they're caught in an awkward position: they must be complicated beasts in order to support the plethora of Javascript out there on the Web (especially since Javascript is so brittle and Web APIs are so numerous and bloated); yet their role as user agent less comprehensive than a Web browser.

For a while now I've been using Firefox to do such scraping, since being a popular user agent means it's more likely that a site will work in it. Unfortunately Firefox is a pain to control: we could use Selenium, but then we'd have two problems (getting our scraper to connect to and control Selenium, and getting Selenium to connect to and control Firefox; not to mention aligning the versions of everything, and presumably the planets too...). I found it easier to use a combination of Xvfb and xdotool to drive Firefox's developer console.

Headless Browsers

Recently both Firefox and Chromium have gained support for headless browsing. On the one hand this is good, since it means we don't have to fight with global nonsense like conflicting Xvfb display numbers; but on the other it means giving up xdotool as a means of controlling the browser.

I decided to give headless Chromium a go, since it can provide a control API over a socket (it can also bind to a localhost port, but that's just introducing another unnecessary global conflict!). After several attempts to pass in sockets via file descriptors, trying to connect as a client and as a server, I gave up and abandoned that approach completely; it seemed almost as bad as Selenium!

Chromium REPL

There is actually a much nicer way to drive Chromium: using it as a Javascript REPL via the --repl argument. This is very similar to the way I drove Firefox from its developer console, but it uses a nice, local stdio interface rather than requiring X11 hacks.

There is some cruft involved, like a lot of verbose messages which we may want to filter out, but underlying that there's a simple mechanism to send in snippets of Javascript and get back JSON. Importantly, we can do all of our control flow, etc. from the "outside" (e.g. in bash) rather than having to write everything in Javascript (like we would for PhantomJS, for example).

Experience So Far

I've made some scrapers using a headless Chromium REPL. A couple of things I've come across: