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 Node.js as the batch controller and PhantomJS as a separate renderer. PhantomJS is a command-line program, not a Node.js module. Your Node process should read URLs, start a bounded number of PhantomJS child processes, pass each URL and output path as arguments, and record each process result. This design still works for legacy capture jobs, but PhantomJS development is suspended and its upstream repository is archived, so validate PhantomJS 2.1.1 on your target operating system before depending on it.
What the architecture looks like
Each screenshot is handled by two programs:
- Node.js controller: loads the URL list, creates collision-safe filenames, limits concurrency, starts PhantomJS, applies a timeout, and reports status.
- PhantomJS script: creates a
webpage, sets the viewport, opens one URL, checks the load status, renders an image, then exits with code 0 or 1.
The PhantomJS FAQ describes this as a “loose binding”: launch a PhantomJS process from Node.js and interact with it. Do not try to require('phantomjs') as though it were a normal browser automation module.
Prerequisites and version caveats
- Node.js with permission to create child processes and write the destination directory.
- A PhantomJS executable available on
PATH, or an absolute path to the executable. - A PhantomJS capture script saved as a separate file.
- A URL list containing absolute
http://orhttps://URLs.
PhantomJS 2.1.1 is the version context used by the project’s command-line documentation; the repository README identifies 2.1 as the latest stable release. The project is archived and read-only, and development is suspended. Expect compatibility issues with modern TLS, JavaScript, sites that require current browser features, and bot protection. Test a representative set of pages on the operating system where the batch will run.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Build the PhantomJS renderer
Create capture.js:
var system = require('system');
var webpage = require('webpage');
var url = system.args[1];
var output = system.args[2];
if (!url || !output) {
console.error('Usage: phantomjs capture.js URL OUTPUT');
phantom.exit(2);
}
var page = webpage.create();
page.viewportSize = { width: 1280, height: 800 };
// Optional crop: uncomment and adjust when you need a fixed region.
// page.clipRect = { top: 0, left: 0, width: 1280, height: 800 };
page.open(url, function (status) {
if (status === 'success') {
page.render(output);
console.log(JSON.stringify({ url: url, output: output, status: status }));
phantom.exit(0);
}
console.error('Failed to load: ' + url + ' (status: ' + status + ')');
phantom.exit(1);
});
Arguments are read through PhantomJS’s system module. viewportSize controls the browser viewport. Set clipRect when you need only a rectangular region rather than the whole rendered viewport. The extension of the output filename determines the intended format; PhantomJS documentation lists PNG, JPEG, GIF, and PDF support. Verify behavior with the installed build when a particular format is important.
#1 Best Overall
Write a bounded Node.js batch controller
Save the following as batch-screenshots.js. It uses only Node’s built-in modules, limits simultaneous PhantomJS processes, applies a per-job timeout, and keeps a result for every input URL.
const fs = require('node:fs');
const path = require('node:path');
const crypto = require('node:crypto');
const { spawn } = require('node:child_process');
const phantom = process.env.PHANTOMJS || 'phantomjs';
const inputFile = process.argv[2] || 'urls.txt';
const outputDir = process.argv[3] || 'shots';
const concurrency = Number(process.env.CONCURRENCY || 3); // tune for your machine
const timeoutMs = Number(process.env.TIMEOUT_MS || 60000);
fs.mkdirSync(outputDir, { recursive: true });
const urls = fs.readFileSync(inputFile, 'utf8')
.split(/r?n/).map(s => s.trim())
.filter(Boolean);
function outputName(url, index) {
const digest = crypto.createHash('sha256').update(url).digest('hex').slice(0, 12);
return path.join(outputDir, `${String(index + 1).padStart(4, '0')}-${digest}.png`);
}
function runOne(url, index) {
return new Promise(resolve => {
const output = outputName(url, index);
const child = spawn(phantom, ['capture.js', url, output], { windowsHide: true });
let stderr = '';
let finished = false;
const timer = setTimeout(() => {
if (!finished) {
finished = true;
child.kill('SIGTERM');
resolve({ url, output, ok: false, error: 'timeout' });
}
}, timeoutMs);
child.stderr.on('data', data => { stderr += data.toString(); });
child.on('error', error => {
if (!finished) {
finished = true; clearTimeout(timer);
resolve({ url, output, ok: false, error: error.message });
}
});
child.on('close', code => {
if (finished) return;
finished = true; clearTimeout(timer);
const ok = code === 0 && fs.existsSync(output);
resolve({ url, output, ok, code, error: ok ? '' : (stderr.trim() || `exit ${code}`) });
});
});
}
async function main() {
const results = [];
let next = 0;
async function worker() {
while (true) {
const index = next++;
if (index >= urls.length) return;
const result = await runOne(urls[index], index);
results.push(result);
console.log(JSON.stringify(result));
}
}
await Promise.all(Array.from({ length: Math.max(1, concurrency) }, worker));
fs.writeFileSync(path.join(outputDir, 'results.json'), JSON.stringify(results, null, 2));
process.exitCode = results.every(r => r.ok) ? 0 : 1;
}
main().catch(error => { console.error(error); process.exitCode = 1; });
Run the batch
- Create
urls.txt, one absolute URL per line. - Run
node batch-screenshots.js urls.txt shots. - Set a different executable with
PHANTOMJS=/opt/phantomjs/bin/phantomjs. - Tune the example limit with
CONCURRENCY=2or another value. No universal safe number is documented; measure memory, CPU, network load, and failure rate on your machine. - Inspect
shots/results.json; a job is successful only when PhantomJS exits 0 and the expected file exists.
Capture dimensions and formats
Viewport versus crop
viewportSize establishes the layout viewport, so responsive pages may render a different navigation or column arrangement at 1280 pixels than at a phone width. clipRect crops the saved result to a rectangle. It does not make the page responsive; change the viewport when you need a different breakpoint.
Output files
Use extensions such as .png, .jpg, .gif, or .pdf according to the formats supported by your installed PhantomJS build. Keep one deterministic output path per URL. The hash-based name in the controller prevents query strings, slashes, and duplicate hostnames from overwriting one another.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Rank #2
Reliability practices for real batches
- Keep input identity: store the original URL, output path, exit code, load status, and stderr in a manifest.
- Reject stale files: check the child’s exit code and verify the file was created during this run; an old file must not count as a new screenshot.
- Use timeouts: a page can wait forever on a resource. Kill the child and mark that URL failed rather than blocking the entire batch.
- Retry selectively: retry transient network failures, but do not loop indefinitely on a page that consistently returns an unsuccessful
page.openstatus. - Protect resources: separate processes consume memory. Increase concurrency only after observing the host under the intended workload.
- Quote safely: pass arguments as the array given to
spawn; do not concatenate untrusted URLs into a shell command.
Troubleshooting
“phantomjs: command not found”
Install or unpack a compatible executable, add its directory to PATH, or set the PHANTOMJS environment variable to its absolute path. Confirm with phantomjs --version.
Every job returns an unsuccessful status
Test one URL directly: phantomjs capture.js https://example.com test.png. Check DNS, proxy and TLS support, then try a simpler page. Modern sites may depend on browser APIs that this legacy engine does not implement.
The process never finishes
Keep the controller timeout enabled. A page-open callback may be delayed by a stalled resource; terminate that child, record the timeout, and continue with the remaining URLs.
Rank #3
Images are blank or incomplete
Require status === 'success' before rendering, and consider a page-specific delay only if your script adds one. PhantomJS does not provide the feature coverage of current browsers, so client-side rendering or lazy content may never appear.
Files overwrite each other
Do not derive names from only the hostname. Include an index and a hash of the complete URL, as the controller does.
When a hosted renderer is a better fit
A local PhantomJS batch gives you process-level control and keeps files on your infrastructure, but you maintain the executable, compatibility workarounds, retries, and capacity. PhantomJSCloud documentation describes a Node.js client and batch requests for managed rendering; verify its current availability, limits, and pricing before choosing it.
Rank #4
Or skip the browser setup
ScreenshotNeo is a current website screenshot API and MCP server. It accepts one GET request, handles the browser, 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. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers identify the page verdict and whether the shot was billed. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for authentication and options. A minimal call is:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
You can also use 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)
Or 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}`);
const fs = require('node:fs');
fs.writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo supports full-page lazy-image loading, CSS-selector element capture, device presets, arbitrary viewports, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its parameter names also accept those used by other screenshot APIs, easing migration. Every plan includes every feature: 1,000 shots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.
Operational checklist
- Pin and test the PhantomJS binary version.
- Run one known-good URL before a large batch.
- Choose viewport and crop dimensions deliberately.
- Use bounded concurrency and a timeout.
- Persist per-URL results and stderr.
- Validate output files, not just process exit codes.
- Plan a migration if modern browser compatibility becomes a requirement.
Frequently Asked Questions
Can PhantomJS take a full-page screenshot automatically?
The documented controls cover the viewport and an optional clip rectangle. A full-page result requires a script and dimensions that match your intended page; PhantomJS does not make modern full-page capture behavior automatic.
Can I run the controller on Windows?
Yes, if a compatible PhantomJS executable runs on that system. Use the executable’s full path in PHANTOMJS and test path permissions, process termination, and output writing before batching.
Why is a separate process preferable to a Node package?
PhantomJS is its own command-line application rather than a Node.js module. A child process preserves that supported integration boundary and gives Node explicit exit codes and stderr to manage.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallQuick Recap
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.

