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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use one webpage object and start the next page.open() from the previous call’s completion callback. Check the callback’s status before processing the page, finish all page-specific work, and call phantom.exit() only after the final URL has been handled. Starting several navigations immediately on the same object creates overlapping loads and nondeterministic results.

The reliable pattern: a callback-driven URL queue

PhantomJS navigation is asynchronous. The optional callback passed to page.open runs when loading completes and receives either success or fail. That makes the callback the hand-off point between pages.

The following script opens three pages in order, processes each only after its load finishes, and exits after the queue is empty:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

var urls = [
  'https://example.com/one',
  'https://example.com/two',
  'https://example.com/three'
];

var index = 0;

function openNext() {
  if (index >= urls.length) {
    phantom.exit();
    return;
  }

  var url = urls[index++];
  page.open(url, function (status) {
    if (status === 'success') {
      console.log('Loaded: ' + url);

      // Read, inspect, or render this page here.
      var title = page.evaluate(function () {
        return document.title;
      });
      console.log('Title: ' + title);
    } else {
      console.log('Failed to load: ' + url);
      // Choose whether to continue, retry, or stop.
    }

    // Start the next navigation only after this callback is complete.
    openNext();
  });
}

openNext();

Save it as multi.js and run it with the PhantomJS executable installed on your system:

#1 Best Overall
The Phantom of the Opera (Full Screen Edition)
  • This Certified Refurbished product is tested and certified to look and work like new. The refurbishing process includes functionality testing, basic cleaning, inspection, and repackaging. The product ships with all relevant accessories, a minimum 90-day warranty, and may arrive in a generic box.
phantomjs multi.js

The important detail is not the recursive function name. It is the sequencing: openNext() is called from inside the callback, after the current page’s inspection or rendering has finished. The same page object is therefore never asked to navigate twice at once.

Why the status check matters

A callback can report fail for a network error, an unreachable host, or another load problem. Do not assume that a failed navigation produced a usable document. Keep logging the URL alongside the status so a long run can be diagnosed. You can continue to the next item, retry a transient failure, or stop the run when a particular URL is essential.

Why an unconditional exit is wrong

This starts an asynchronous operation and then terminates the process before its callback can run:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
page.open('https://example.com', function (status) {
  console.log(status);
});
phantom.exit(); // Too early

Place phantom.exit() in the queue’s completion branch, or in an equivalent “all work finished” path. Otherwise PhantomJS may end before later pages are opened, or it may remain alive after your work is complete.

Doing useful work for every page

Put page-specific logic in the successful branch of the callback. For example, this version writes a screenshot for each URL and gives files stable names:

var webpage = require('webpage');
var page = webpage.create();
var urls = [
  'https://example.com/one',
  'https://example.com/two'
];
var index = 0;

page.viewportSize = { width: 1440, height: 900 };

function openNext() {
  if (index >= urls.length) {
    phantom.exit();
    return;
  }

  var number = index;
  var url = urls[index++];

  page.open(url, function (status) {
    if (status === 'success') {
      page.render('page-' + number + '.png');
      console.log('Rendered ' + url);
    } else {
      console.log('Could not render ' + url);
    }
    openNext();
  });
}

openNext();

Keep all processing that depends on the loaded page before the call to openNext(). If you need to write data asynchronously outside PhantomJS’s normal page APIs, add your own completion signal and advance only after that work is done.

Reading the DOM with page.evaluate

page.evaluate executes in the loaded page’s context, so it can access document and the DOM. Values crossing back to the outer script must be simple serializable values. Functions, closures, and DOM nodes cannot be passed directly between the two contexts.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var result = page.evaluate(function () {
  var links = document.querySelectorAll('a');
  var values = [];
  for (var i = 0; i < links.length; i++) {
    values.push({
      text: links[i].textContent,
      href: links[i].href
    });
  }
  return {
    title: document.title,
    links: values
  };
});

console.log(JSON.stringify(result));

Keep the URL list, counters, retry policy, file paths, and the phantom object in the outer script. Use evaluate only for page-side operations and return strings, numbers, booleans, arrays, or plain objects composed of those values.

Rank #3
The Phantom of the Opera (Two-Disc Special Edition)
  • DVD
  • AC-3, Closed-captioned, Color
  • English (Subtitled), Spanish (Subtitled), French (Subtitled)
  • 2
  • 141

Handling failures, retries, and an incomplete run

Continue after a failed URL

The basic queue already continues after fail. This is appropriate for batch collection where one unavailable site should not block all other URLs. Record the failure in a report or write it to standard output.

Retry a URL a limited number of times

Retries must be bounded. Otherwise a permanently broken URL can prevent phantom.exit() forever.

var webpage = require('webpage');
var page = webpage.create();
var urls = [
  'https://example.com/one',
  'https://example.com/two'
];
var index = 0;
var attempts = 0;
var maxAttempts = 2;

function openNext() {
  if (index >= urls.length) {
    phantom.exit();
    return;
  }

  var url = urls[index];
  page.open(url, function (status) {
    if (status === 'success') {
      console.log('Loaded: ' + url);
      index++;
      attempts = 0;
      openNext();
      return;
    }

    attempts++;
    console.log('Failed (' + attempts + '): ' + url);
    if (attempts < maxAttempts) {
      openNext();
    } else {
      index++;
      attempts = 0;
      openNext();
    }
  });
}

openNext();

This example retries the current item once after the first failure, then records it as skipped and advances. Adjust the policy for your workload; the callback status alone does not tell you whether another attempt will succeed.

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

Stop on the first fatal failure

If every page is required, call phantom.exit(1) (or your chosen failure path) from the failed branch after logging the URL. Do not silently render an error page as if it were the requested document.

Rank #4
Sale
Phantom of the Opera
  • Format: Closed-captioned, Color, Dolby, NTSC, Subtitled, Widescreen
  • Language: English (Dolby Digital 5.1), French (Dolby Digital 5.1)
  • Subtitles: English, French, Spanish
  • Region 1 (U.S. and Canada only); Number of discs: 1
  • Rated: PG-13; Run Time: 141 minutes

One page object or several?

Reuse one object for ordered work

One object is the clearest choice when order matters, when later decisions depend on earlier output, or when all pages can share the same viewport and handlers. It prevents overlapping navigation on one object and makes the exit condition straightforward. Remember that navigation replaces the current page state, so extract or save anything you need before opening the next URL.

Create separate objects for independent state

Create an object with require('webpage').create() for each independent workflow when pages need different cookies, viewport settings, event handlers, or simultaneous activity. A sketch looks like this:

var webpage = require('webpage');
var pending = 2;

function finished() {
  pending--;
  if (pending === 0) {
    phantom.exit();
  }
}

function load(url) {
  var page = webpage.create();
  page.open(url, function (status) {
    console.log(url + ': ' + status);
    if (status === 'success') {
      // Process this page before releasing it.
      console.log(page.evaluate(function () { return document.title; }));
    }
    finished();
  });
}

load('https://example.com/one');
load('https://example.com/two');

This enables concurrent loads, but the available PhantomJS documentation does not define a universal safe concurrency limit or promise a particular memory cost. Test the number of simultaneous pages in the version and environment you actually deploy. Track every callback and exit only when the full count reaches zero.

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

Common problems and fixes

Symptom Likely cause Fix
Only the first URL opens The next page.open is never called, or it is called outside the callback. Call your queue function at the end of every callback branch.
Pages appear mixed or a screenshot is from the wrong URL The same page object received overlapping navigations. Serialize navigation with the callback, or use separate page objects.
The process exits immediately phantom.exit() runs directly after starting an asynchronous open. Move exit to the “queue empty” or “all callbacks finished” path.
The script never terminates There is no final exit, or a retry loop has no bound. Call phantom.exit() after the last callback and cap retries.
status is fail The URL did not load successfully. Log the URL, skip or retry it, and do not inspect or render it as a valid page.
DOM data is missing The code ran outside page.evaluate, or a non-serializable value was returned. Perform DOM access inside evaluate and return plain serializable data.
A later page has the wrong cookies or handlers State from a reused page object is shared. Reset what is reusable, or create a separate webpage object for that workflow.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Operational guidance for long URL lists

  • Log an index, URL, status, and elapsed time for every callback.
  • Write output using deterministic names derived from the queue index rather than a URL’s punctuation.
  • Keep retries finite and distinguish skipped URLs from successfully processed ones.
  • Do not assume a successful load means that a particular application element is ready; if your page depends on client-side rendering, use an appropriate readiness strategy available in your PhantomJS version and validate the resulting DOM.
  • Verify scripts against the PhantomJS version installed in production. PhantomJS documentation and examples are historical, and behavior can differ from the environment in which an old script was written.

Or skip the browser setup

If your goal is dependable screenshots rather than maintaining PhantomJS navigation code, ScreenshotNeo provides a website screenshot API and MCP server. One request captures a URL as PNG, JPEG, WebP, or PDF, while its capture flow accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Each response identifies whether it was a clean page, a bot check, blank page, timeout, failed load, or cache hit; only clean shots are billed.

For a single capture, see the ScreenshotNeo API documentation:

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

Equivalent 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)

Equivalent 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}`);

ScreenshotNeo also offers full-page and element captures, device presets and custom viewports, retina scale, dark mode, PDFs with paper and page-range controls, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable 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.

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

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

Frequently Asked Questions

Can I use one PhantomJS page object for unrelated domains?

Yes, provided each navigation completes and you finish extracting or saving the current page before opening the next. Use separate objects when independent cookies, handlers, or viewport state must coexist.

Does a successful page.open guarantee that JavaScript-rendered content is ready?

No. It reports the navigation outcome. Your script still needs to verify the application-specific DOM state before collecting data or rendering.

How do I know whether to serialize or parallelize a batch?

Serialize when ordering and simple control are more important than throughput. Use multiple webpage objects only for genuinely independent work, and establish a concurrency level by testing your deployed PhantomJS version and host.

Quick Recap

SaleBestseller No. 2
Bestseller No. 3
The Phantom of the Opera (Two-Disc Special Edition)
The Phantom of the Opera (Two-Disc Special Edition)
DVD; AC-3, Closed-captioned, Color; English (Subtitled), Spanish (Subtitled), French (Subtitled)
$12.99
SaleBestseller No. 4
Phantom of the Opera
Phantom of the Opera
Format: Closed-captioned, Color, Dolby, NTSC, Subtitled, Widescreen; Language: English (Dolby Digital 5.1), French (Dolby Digital 5.1)
$9.99

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.

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