Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

You can capture several HTML pages in PhantomJS by storing URL/output pairs in an array, opening each page with page.open(), checking the callback status, and calling page.render() only after a successful load. The sequential script below writes one file per URL and avoids overwriting earlier captures.

Important: PhantomJS development is suspended until further notice, and its latest documented release is 2.1.1. Treat this as a legacy workflow: modern sites may render differently or fail where current browsers succeed.

What you need before running the batch capture

  • PhantomJS 2.1.1 (the version covered by its CLI documentation) installed and available as phantomjs.
  • A JavaScript file, such as capture-pages.js.
  • Write permission in the directory where screenshots will be saved.
  • A list of reachable URLs and unique output filenames.

PhantomJS uses a scriptable JavaScript API backed by QtWebKit. Because the project is no longer actively developed, test representative pages before relying on it for production archives, visual regression checks, or sites that require modern browser APIs.

Complete sequential script for multiple pages

This implementation adapts the documented single-page operations to a URL/output list. It deliberately processes one page at a time; the reviewed PhantomJS references document individual loads and renders, not a canonical concurrent batch implementation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var webpage = require('webpage');

var pages = [
  { url: 'https://example.com/one', output: 'one.png' },
  { url: 'https://example.com/two', output: 'two.png' },
  { url: 'https://example.com/three', output: 'three.pdf' }
];

var index = 0;

function captureNext() {
  if (index >= pages.length) {
    phantom.exit();
    return;
  }

  var item = pages[index++];
  var page = webpage.create();

  page.open(item.url, function (status) {
    if (status === 'success') {
      page.render(item.output);
      console.log('Saved ' + item.url + ' to ' + item.output);
    } else {
      console.log('Could not load ' + item.url + ': ' + status);
    }

    page.close();
    captureNext();
  });
}

captureNext();

Run it from the directory containing the script:

phantomjs capture-pages.js

The callback receives a load status. A success status leads to page.render(); any other status is logged and the script advances to the next item. The final call to phantom.exit() terminates the process after the list is exhausted.

Why sequential processing is the safe default

Each iteration creates a page, waits for its callback, renders, closes the page, and then starts the next URL. This keeps memory use and network activity more predictable and makes failures attributable to a specific item. Do not assume that launching many page objects in parallel will improve throughput: the supplied PhantomJS documentation does not establish a supported multi-page concurrency pattern, and older WebKit builds can behave unpredictably under load.

Use unique names

Every object should have a distinct output value. If two URLs point to the same filename, the later render replaces the earlier file. For generated names, include an index or a sanitized page identifier, for example 001-home.png and 002-pricing.png.

Control the viewport and capture region

Set page.viewportSize before opening the URL when you need a consistent browser window. Set page.clipRect when you want only a rectangle rather than the entire viewport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var page = webpage.create();
page.viewportSize = { width: 1440, height: 900 };
page.clipRect = { top: 0, left: 0, width: 1440, height: 500 };

page.open('https://example.com', function (status) {
  if (status === 'success') {
    page.render('hero.png');
  }
  page.close();
});

viewportSize controls the layout width and height presented to the page. clipRect limits the rendered area to the specified top, left, width, and height. Apply the same values to every page in a batch when you need comparable images. A full-page result is not guaranteed merely by using a tall viewport; PhantomJS’s legacy rendering behavior and page structure determine what is included.

Per-page settings

If different targets require different dimensions, put settings in each list item and assign them before page.open():

var pages = [
  { url: 'https://example.com/desktop', output: 'desktop.png', width: 1440, height: 900 },
  { url: 'https://example.com/mobile', output: 'mobile.png', width: 390, height: 844 }
];

function captureNext() {
  if (index >= pages.length) { phantom.exit(); return; }
  var item = pages[index++];
  var page = webpage.create();
  page.viewportSize = { width: item.width, height: item.height };
  page.open(item.url, function (status) {
    if (status === 'success') page.render(item.output);
    page.close();
    captureNext();
  });
}

Choose PNG, JPEG, or PDF output

Format Use it when Relevant behavior
PNG You need lossless pixels, text comparison, or transparency where supported. Usually larger than JPEG but preserves exact image detail.
JPEG You need smaller photographic files. Quality is configurable on a 0–100 scale; the documented default is 75.
PDF You need document-style output for printing or distribution. Page layout follows PhantomJS/Qt rendering and may differ from a current browser.
BMP or PPM You need one of the additional formats exposed by the API. Choose the extension that matches the required downstream tool.
GIF Your Qt build provides GIF support. Support depends on the Qt build; do not assume it is available everywhere.

page.render(filename) selects the format from the filename extension. For JPEG, configure quality before rendering according to the PhantomJS build’s supported page settings. Keep the same format and quality across a batch if files will be compared.

Make batches more dependable

Record outcomes

Log the URL, status, and output path. A failed page should not silently produce a misleading image. You can also maintain a failure list and exit with a nonzero status in your surrounding shell or build system after PhantomJS finishes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Separate input from code

For a small, fixed set, an array is easiest to review. For larger sets, generate the JavaScript list from your build process, taking care to escape quotes and reject invalid URLs before invoking PhantomJS.

Allow for page timing

page.open() reports the load callback status, but a page can continue changing after its initial load because of scripts, fonts, or asynchronous requests. PhantomJS is a legacy engine, so there is no universal modern “network idle” guarantee in the documented pattern. If a target needs extra time, use a controlled timer in the page script and render after that delay; validate the result rather than assuming the delay is sufficient.

Keep runs reproducible

  • Fix viewport dimensions and output format.
  • Use stable, unique filenames.
  • Run from a known working directory.
  • Store the PhantomJS version with the job configuration.
  • Compare captures visually before treating differences as site changes; the old WebKit engine itself can be the source of a difference.

Troubleshooting common failures

Status is not success

Check the URL from the same machine, DNS and TLS availability, and whether the site blocks old user agents or requires a browser capability PhantomJS lacks. Keep the failure in the log and continue to the next item rather than rendering an incomplete page.

The output file is blank or incomplete

The page may depend on asynchronous JavaScript, resources that have not finished loading, or APIs unsupported by QtWebKit. Try a modest post-load timer, verify the viewport, and inspect the page in a current browser. If the site fundamentally requires modern APIs, PhantomJS is the wrong engine.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Several pages overwrite one another

Inspect every output value for duplicates. Include an index, slug, or hash in each filename and create the destination directory before starting the batch.

Only part of the page appears

Check viewportSize and clipRect. Remove or enlarge the clip rectangle when you intended a viewport capture. Long, dynamically expanding pages may need a page-specific strategy; the documented operations do not promise automatic full-page stitching.

PDF or GIF output does not work

Confirm the extension and the capabilities of the PhantomJS/Qt build installed on the machine. PNG and JPEG are the safest common image choices; GIF support is build-dependent.

The process never exits

Ensure every code path eventually calls page.close() and then captureNext(), and that the completion branch calls phantom.exit(). A callback that returns early without advancing the index can leave the batch waiting indefinitely.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When a hosted renderer is a better fit

Local PhantomJS gives you a script and local files, but it also leaves you responsible for installing a suspended project, handling old-browser compatibility, retries, storage, and page-specific failures. A hosted renderer can be preferable when you need current infrastructure or a service API. PhantomJsCloud documents hosted rendering, screenshots, and automation such as multi-page navigation and multiple renders; verify its current availability and terms directly before adopting it.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It accepts one GET request and returns PNG, JPEG, WebP, or PDF. Before capture, it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing state in X-Page-Verdict and X-Billed headers.

See the parameter reference in the ScreenshotNeo documentation. A direct cURL batch can be built around one call per URL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

It also supports full-page captures with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper/margin/page-range controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to start.

PhantomJS or ScreenshotNeo: a practical decision

Need Better starting point Reason
Offline script and local files PhantomJS The sequential JavaScript pattern runs locally, but you must accept its suspended status and legacy engine.
Modern hosted capture with cleanup ScreenshotNeo Consent banners, popups, and chat widgets are removed before capture, with explicit billing verdict headers.
AI-agent workflow ScreenshotNeo The MCP server provides screenshot, page-info, and PDF tools.
Large URL batches ScreenshotNeo Bulk capture supports up to 100 URLs per call.

Frequently Asked Questions

Can PhantomJS capture several URLs in parallel?

The documented references show single-page loading and rendering, not a canonical concurrent batch pattern. Sequential processing is the defensible default.

Which PhantomJS version does the documentation describe?

The CLI documentation applies to the latest release identified there, PhantomJS 2.1.1.

Does page.render automatically wait for every modern web request?

No universal modern network-idle guarantee is established by the documented pattern. Asynchronous pages may need a validated delay or a different rendering engine.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.