Saving Screenshots in Rails with url2png and Paperclip
url2png is a service for generating screenshots of websites. Pass in a URL and some dimensions and it spits back a high quality png capture of that site.
Unlike some competing services I've tried, it even does a decent job handling sites that require client-side rendering.
Someone has already built a ruby gem for working with url2png. It provides a rails helper for hot-linking url2png images in your views. Perhaps a better name for that gem would be url2png-rails.
Though useful, this is not what I was looking for. Instead, I wanted the ability to save a local copy of the screenshots on my own server. Since I was already using Paperclip for saving attachments, this turned out to be easy.
An API wrapper
The url2png API is quite simple. Using it requires building the URL of the image by generating a token. The following uses v3 of their API. As of writing, this is the version they use in their guide.
require 'digest/md5'
class ScreenShot
KEY = 'your key'
SECRET = 'your secret'
def initialize(url, bounds)
@url = url
@bounds = bounds
end
def token
Digest::MD5.hexdigest("#{SECRET}+#{@url}")
end
def img_url
"http://api.url2png.com/v3/#{KEY}/#{token}/#{@bounds}/#{@url}"
end
end
Using this, we can easily get the URL of a screenshot of the front page of reddit
>> shot = ScreenShot.new('http://reddit.com', '200x200')
>> shot.img_url
...
http://url2png.../reddit.png
Saving it with Paperclip
Paperclip is a popular gem for managing file attachments in rails applications. Until now, I'd only used it to save files that were passed in through a form. But, it is not restricted to handling POST data or files already on disk. Pass in any IO and it will take care of the rest.
Given a Website model with a url attribute, we can fetch an image for that URL and save an associated screenshot.
class Website
has_attached_file :screenshot,
:styles => {:thumb => '50x50', :square => '200x200' }
def gen_screenshot!
shot = ScreenShot.new(url, '200x200')
self.screenshot = open(shot.img_url)
save!
end
end
Notice that we can pass the IO returned by open directly to Paperclip without having to bother saving it to disk ourselves.
If the image is small enough, behind the scenes open will use a StringIO and hold the image data in memory. This avoids the filesystem overhead of writing an extra TempFile.
We can attach the image to our Website model like this:
>> site = Website.new(url: 'http://reddit.com')
>> site.gen_screenshot!
Paperclip will handle the messy details of thumbnail generation. When it's done, it will move the files to the proper location on disk.
Better as a Gem
Since this is a such a small amount of code, bundling it as a gem may seem like overkill. But, I would argue that it is still the right move. Perhaps someday this functionality could be integrated into @wout's url2png gem.