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

Use one webpage object and start each new page.open() from the previous call’s completion callback. Check the callback status, finish that page’s work, then advance an index to the next URL. Call phantom.exit() only after the final callback has completed. This callback chain prevents overlapping navigation on one page and prevents PhantomJS from exiting before asynchronous work finishes.

The reliable pattern: open URLs sequentially

page.open() is asynchronous. Its optional callback receives a status string: success when loading completed and fail when it did not. The next navigation must therefore happen inside the callback, after you have inspected or rendered the current page.

The following script reuses one page object, processes three URLs in order, and exits only when the list is exhausted:

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);
      console.log('Title: ' + page.title);

      // Put page-specific DOM work or rendering here.
      // page.render('page-' + index + '.png');
    } else {
      console.log('Failed to load: ' + url);
    }

    // Do not navigate until this callback's work is complete.
    openNext();
  });
}

openNext();

The callback closes over url, so the log identifies the page that produced the result even after the index has advanced. Replace the comment with your extraction, screenshot, or file-writing logic. Keep that work inside the callback; otherwise the script can navigate away before it reads the document.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#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.

Why an unconditional exit fails

This is unsafe:

page.open('https://example.com/one', function () {
  console.log(page.title);
});
page.open('https://example.com/two', function () {
  console.log(page.title);
});
phantom.exit();

Both opens are asynchronous, and the immediate phantom.exit() can terminate the process before either callback runs. Starting a second navigation on the same page also replaces the first navigation’s state. The documented lifecycle requires you to wait for the callback and to exit at the end of the workflow.

Processing each page before moving on

Put all operations that depend on the loaded page between the status check and the call to openNext(). For example, this version extracts a simple, serializable record:

var webpage = require('webpage');
var page = webpage.create();
var urls = [
  'https://example.com/one',
  'https://example.com/two'
];
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(JSON.stringify({ url: url, status: status }));
      openNext();
      return;
    }

    var result = page.evaluate(function () {
      return {
        title: document.title,
        heading: document.querySelector('h1') ?
          document.querySelector('h1').textContent : null
      };
    });

    console.log(JSON.stringify({
      url: url,
      status: status,
      title: result.title,
      heading: result.heading
    }));

    openNext();
  });
}

openNext();

page.evaluate() runs in the loaded page’s context. Only simple serializable arguments and return values cross that boundary. Pass strings, numbers, booleans, arrays, and plain objects; do not try to return a DOM node, a closure, or a PhantomJS object. Keep the URL list, counters, file operations, and phantom calls in the outer script.

Handling failures without losing the run

The callback status is the first failure signal to check for every URL. A failed page should not be rendered or queried as though it loaded successfully. Decide explicitly whether a failure is recoverable:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Continue: log the URL and status, then call openNext() so later URLs are attempted.
  • Stop: record the failure and call phantom.exit() when one missing page makes the result unusable.
  • Retry: keep a retry counter outside the callback and re-open the same URL only up to a defined limit. Advance the index after the retry policy is exhausted.

Here is a bounded retry implementation. It retries a URL twice, then records it as failed and continues:

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 = 3;

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('Attempt ' + attempts + ' failed for ' + url);

    if (attempts < maxAttempts) {
      openNext();
      return;
    }

    console.log('Giving up on: ' + url);
    index++;
    attempts = 0;
    openNext();
  });
}

openNext();

Retries should be deliberate: a persistent DNS, TLS, server, or page-script problem will not be fixed by an unlimited loop. Always log the URL, status, attempt number, and any output needed to diagnose a long batch.

When separate page objects are better

Reuse one object when ordering matters, when each page depends on the previous result, or when you want the simplest lifecycle to reason about. A single object also avoids accidental sharing of cookies, viewport settings, and page event handlers between concurrent tasks.

Create independent objects when pages need separate state or when overlapping loads are useful. Each object must have its own callback, and the process must exit only after all intended callbacks have finished. PhantomJS documentation describes webpage instances, but it does not establish a universal safe concurrency limit or a benchmarked memory cost. Treat concurrency as an environment-specific choice to measure rather than a guaranteed capacity.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var webpage = require('webpage');
var urls = [
  'https://example.com/one',
  'https://example.com/two',
  'https://example.com/three'
];
var remaining = urls.length;

function finishOne() {
  remaining--;
  if (remaining === 0) {
    phantom.exit();
  }
}

for (var i = 0; i < urls.length; i++) {
  (function (url) {
    var page = webpage.create();
    page.open(url, function (status) {
      if (status === 'success') {
        console.log('Loaded: ' + url);
        // Read or render this page here.
      } else {
        console.log('Failed: ' + url);
      }
      finishOne();
    });
  })(urls[i]);
}

The immediately invoked function preserves the current URL for each callback. Without that closure, callbacks can read a later loop value. This pattern has no ordering guarantee: output may arrive in whichever order pages complete. If order matters, use the sequential version instead.

Choosing between sequential and concurrent loading

Requirement Recommended design Reason
The next URL depends on data from the current page One page object, callback chain The next call starts only after processing finishes.
Results must be emitted in URL-list order Sequential reuse Completion order cannot reorder the output.
Pages need independent cookies or other state Separate webpage objects Each object has its own page lifecycle.
Loads can overlap and order is irrelevant Multiple objects with a completion counter The process exits after the last callback.
You do not know the runtime’s resource limits Start sequentially, then test small batches The cited documentation provides no universal concurrency number.

Lifecycle and cleanup checklist

  • Declare the URL list and an index or completion counter outside callbacks.
  • Call page.open() only when the page object is ready for another navigation.
  • Inspect status before reading the DOM or rendering.
  • Complete extraction, rendering, and logging before advancing.
  • For concurrent objects, decrement a shared counter exactly once per callback.
  • Call phantom.exit() in the final branch, not immediately after scheduling asynchronous work.
  • Log failures with their URL so a partial batch can be rerun.

Troubleshooting common failures

The script exits before pages finish

Cause: phantom.exit() runs immediately after an asynchronous call. Fix: move it into the end-of-list branch for sequential code, or into the counter reaching zero for concurrent code.

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

Only the last URL appears to load

Cause: multiple page.open() calls were started on the same page without waiting for callbacks. Fix: call the next open from the current callback and process the current page first.

A callback reports fail

Cause: the page did not load successfully. Fix: do not call evaluate() or render() for that result; log it and apply a clear continue, stop, or bounded-retry policy.

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

Data belongs to the wrong URL

Cause: a loop variable was shared by asynchronous callbacks, or page work occurred after another navigation began. Fix: capture the URL in a closure and keep all page-specific work inside its callback.

evaluate() returns an unusable value

Cause: a DOM node, function, or closure was returned across the page-context boundary. Fix: return plain serializable data and perform orchestration in the outer PhantomJS context.

The process never terminates

Cause: no final phantom.exit() was reached. Fix: verify that the empty-list branch, final sequential callback, or concurrent completion counter is reachable on both success and failure paths.

Rank #4
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
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is dependable screenshots rather than maintaining PhantomJS navigation code, ScreenshotNeo provides a single HTTP request for a clean PNG, JPEG, WebP, or PDF. Its API accepts the URL and returns the capture; documentation and all options are at https://screenshotneo.com/docs/.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, ScreenshotNeo can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each 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 result in X-Page-Verdict and X-Billed headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Frequently asked questions

Can one PhantomJS page object preserve cookies between URLs?

Yes, reusing the object keeps its page state unless your script changes or clears it. Use separate objects when isolation is required.

Does sequential loading guarantee that every network request has finished?

No. The callback reports the completion status of the page open operation. If an application continues fetching data after the initial load, add an application-specific wait strategy before processing.

Can I render each page to a different file?

Yes. Generate a unique filename from the index or another sanitized identifier inside the successful callback, then call page.render() before advancing.

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

Is PhantomJS concurrency safe at any fixed number?

No universal limit is established by the cited PhantomJS documentation. Measure memory, timing, and failure behavior in the exact runtime and workload you operate.

Frequently Asked Questions

Can one PhantomJS page object preserve cookies between URLs?

Yes, reusing the object keeps its page state unless your script changes or clears it. Use separate objects when isolation is required.

Does sequential loading guarantee that every network request has finished?

No. The callback reports the completion status of the page open operation. If an application continues fetching data after the initial load, add an application-specific wait strategy before processing.

Can I render each page to a different file?

Yes. Generate a unique filename from the index or another sanitized identifier inside the successful callback, then call page.render() before advancing.

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

Is PhantomJS concurrency safe at any fixed number?

No universal limit is established by the cited PhantomJS documentation. Measure memory, timing, and failure behavior in the exact runtime and workload you operate.

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
Bestseller 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)
$11.97
Bestseller No. 5

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.