Generating Website Screenshots with PHP

25th January 2016

In this post, I'm going to introduce a PHP package I recently released for generating screenshots of web pages. It's pretty straightforward to get up-and-running, as I'll demonstrate.

Why Generate Screenshots?

There are a number of reasons you may wish to generate screenshots. My motivation behind the package originally was to build a simple "directory" of websites, using screenshots to illustrate the sites.

Screenshots are also useful in testing; you could even run a script to periodically generate a screenshot of one of the sites you maintain in order to check that everything is rendering okay.

What are the Options?

Before I introduce the package, it's worth mentioning that there are a number of third-party services which make it even easier to generats screenshots such as Browshot, URL2PNG, Shrink the Web and Urlbox - but it's worth noting that these are all paid services. If you'd rather do it yourself, or do it on-the-cheap, you might like to try out my package.

How does it Work?

The package uses PhantomJS to generate screenshots. PhantomJS is basically a scriptable, headless web browser. Essentially what it does is launch a "virtual" web browser, navigate to the page, wait a short while for it to download and render and then takes a snapshot, saving it to an image file. It's worth bearing in mind that part about waiting for the page to download and render, as that wait time is something you may find that you need to tweak. But that's getting ahead of ourselves, rather - so let's look at how to get up-and-running with the package.

Getting Started

The package is available on Packagist, and also on Github.

Installation is via Composer:

composer require lukaswhite/screenshotter

There is one thing to mention before you can use it; the package includes a bin directory which contains the PhantomJS executables; you must, therefore, ensure that its contents are executable. For example:

chmod -R +x vendor/lukaswhite/screenshotter/src/bin

Depending on your set-up and permissions, you may find that this is taken care of for you by Composer; specifically, the post-install command.

Basic Usage

The first thing you'll need to do is create an instance of the Screenshotter class:

$screenshotter = new \Lukaswhite\Screenshotter\Screenshotter(
  $ouputPath, 
  $cachePath
);

The $outputPath indicates where the resulting screenshots will go; make sure it's writeable. $cachePath is used to generate small text files which are used to run the service; your best bet here is just to provide a temporary directory.

So, for example:

$screenshotter = new \Lukaswhite\Screenshotter\Screenshotter(
  '/var/www/example.com/app/storage/screenshots/',
  '/var/tmp/'
);

Here's a Laravel example:

$screenshotter = new \Lukaswhite\Screenshotter\Screenshotter(
  storage_path('screenshots'),
  '/var/tmp/'
);

Once you've created an instance, you can use it as follows:

$screenshot = $screenshotter->capture(
  $url, 
  $filename, 
  $options = []
);

Let's look at those parameters:

  • $url is the URL of the website / web page you want to take a screenshot of.
  • $filename is the filename to save the screenshot to.
  • $options is an optional array of additional options:
    • $width; if you don't provide this, it's set to 1024px
    • $height; if you don't provide this, it's set to 768px
    • $clipW; the width to clip (optional)
    • $clipH; the height to clip (optional)

Most of those should be pretty self explanatory, but let's look at $clipW and $clipH in slightly more detail.

As stated above, by by default the screenshot will be based on a web browser "window" set to 1024 by 768 pixels. This, of course, is configurable. However the height of the resulting image could be anything; it's entirely dependent on the length of the page. If you want to restrict the screenshot to a particular size, you'll need to set $clipW and $clipH. For example, to ensure that the screesnhot is exactly 1024 by 768 pixels;

$screenshot = $screenshotter->capture(
  'http://www.lukaswhite.com',
  'lukaswhitedotcom.png',
  [
    'clipW' => 1024,
    'clipH' => 768
  ]
);

To make it 640 x 480 pixels:

$screenshot = $screenshotter->capture(
  'http://www.lukaswhite.com',
  'lukaswhitedotcom.png',
  [
    'width' => 640,
    'height' => 480,
    'clipW' => 640,
    'clipH' => 480
  ]
);

...and so on.

That's all there is to it. However, it's worth looking at some additional options.

Additional Configuration

The wait time

You'll recall that I mentioned the package "waits" a short time while the page has a chance to render. The time it takes to do so can vary enormously, of course; particularly if it uses a lot of front-end scripting. By default it waits for one second - specifically, 1000 milliseconds.

To change that, you can use the following method on the Screenshotter class:

setWait( $value )

The value should be in milliseconds; do, for example:

$screenshotter->setWait( 3000 ); // wait for three seconds

You may find that you need to play around with this a while to get it right.

Setting the SSL Protocol

Phantomjs sometimes has difficulty connecting to HTTPS sites. Chances are if that's the case, the process will appear to suceed but the screenshot will be blank.

In many cases this is because by default PhantomJS uses SSLv3 to connect, which is often either unsupported or - thanks to the POODLE attack - has been disabled.

To get around this, you can use the setSSLProtocol() method to explicitly tell Phantomjs which SSL protocol to use; the following values are valid:

  • sslv3
  • sslv2
  • tlsv1
  • any

In most cases, the simplest approach is simply to set it to all, i.e.:

$screenshotter->setSSLProtocol( 'any' );

Ignoring SSL Errors

Certain SSL errors can also cause difficulty taking screenshots; most notably, expired or self-signed certificates. You can tell PhantomJS to ignore SSL errors with the following:

$screenshotter->ignoreSSLErrors();
// or
$screenshotter->ignoreSSLErrors( TRUE );

To tell it not to ignore SSL errors - which is the default behaviour - simply set it to FALSE:

$screenshotter->ignoreSSLErrors( FALSE );

A Note on Exceptions

If the screenshot process fails, it'll throw an Exception. Most likely this will be an instance of:

Symfony\Component\Process\Exception\ProcessTimedOutException

This type of exception (a timeout) can happen for a variety of reasons, and not just a timeout; because PhantomJS is an external process, it's not always easy to know what failed, so check your parameters. You can also increase the timeout:

$screenshotter->setTimeout(60); // Set timeout to 60 seconds, instead of the defaut 10

Problems?

If you have any problems using the package, please use the Issues Queue on Github.

Wrapping Up

I looked at a couple of possible uses for the package, but if you use it for anything else then please do let me know in the comments!