The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Use one Puppeteer browser, process URLs through a bounded set of pages, wait for each site’s real readiness signal, and save every result under a deterministic filename. A sequential loop is simplest for small lists. For larger batches, a worker pool limits CPU and memory use while allowing one failed URL to be recorded without losing the rest.
What the workflow does
Puppeteer’s Page.screenshot() method captures a page and, when given path, writes the image to disk. One Browser can contain multiple Page instances, so a batch can reuse one browser process rather than launching a browser for every URL. This is an implementation pattern supported by Puppeteer’s browser model, not a published throughput benchmark.
The reliable sequence is:
- Read and validate the URL list.
- Launch one browser.
- Create a page for each job.
- Set a deliberate viewport and capture settings.
- Navigate with an appropriate wait condition.
- Wait for a site-specific selector when necessary.
- Capture the page and record success or failure.
- Close each page in
finally, then close the browser.
Install Puppeteer and prepare inputs
Use a current Node.js project and install Puppeteer:
npm init -y
npm install puppeteer
Puppeteer is guaranteed to work with its bundled browser. If you use executablePath for a system browser, compatibility is not guaranteed. Browser launch has a documented default startup timeout of 30 seconds; set a longer timeout only when your environment needs it.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows 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 reinstall#1 Best Overall
Create urls.txt with one absolute HTTP(S) URL per line:
https://example.com
https://stripe.com
https://www.mozilla.org/
Simple sequential capture
Sequential processing uses one page at a time. It has the smallest memory footprint and makes rate limiting easy to understand.
const fs = require('node:fs/promises');
const path = require('node:path');
const puppeteer = require('puppeteer');
const urls = (await fs.readFile('urls.txt', 'utf8'))
.split(/r?n/)
.map(s => s.trim())
.filter(Boolean);
await fs.mkdir('screenshots', { recursive: true });
const browser = await puppeteer.launch({ headless: true });
function fileName(url, index) {
const host = new URL(url).hostname.replace(/[^a-z0-9.-]/gi, '_');
return path.join('screenshots', `${String(index).padStart(4, '0')}-${host}.png`);
}
const results = [];
try {
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
const page = await browser.newPage();
try {
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
const response = await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 60000
});
if (response && response.status() >= 400) {
throw new Error(`HTTP ${response.status()}`);
}
await page.screenshot({
path: fileName(url, i),
fullPage: true,
type: 'png'
});
results.push({ url, ok: true });
} catch (error) {
results.push({ url, ok: false, error: error.message });
} finally {
await page.close();
}
}
} finally {
await browser.close();
}
await fs.writeFile('results.json', JSON.stringify(results, null, 2));
console.table(results);
networkidle2 is the condition used in Puppeteer’s official screenshot example: it waits until there are no more than two active network connections. It is not a universal guarantee that an application has finished rendering. A continuously connected dashboard, stream, or analytics-heavy page may never reach the state you expect.
Use readiness checks that match the site
Wait for a known element
If the page has a stable completion marker, wait for it after navigation:
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForSelector('[data-page-ready="true"]', { timeout: 30000 });
await page.screenshot({ path: output, fullPage: true });
Use a selector that belongs to the application, such as a report container or product heading. Avoid selectors that appear briefly during loading.
Wait for a controlled delay only when necessary
await page.waitForTimeout(2000);
A fixed delay is predictable but not adaptive: it can waste time on fast pages and still be too short on slow ones. Prefer a selector or application signal when available.
Handle lazy-loaded content
For long pages, full-page capture may trigger layout and lazy-loading behavior differently across sites. Scroll in increments before the final capture when images are loaded only near the viewport:
await page.evaluate(async () => {
await new Promise(resolve => {
let y = 0;
const step = 700;
const timer = setInterval(() => {
window.scrollBy(0, step);
y += step;
if (y >= document.body.scrollHeight) {
clearInterval(timer);
window.scrollTo(0, 0);
resolve();
}
}, 100);
});
});
Bounded parallel workers for larger batches
Parallel pages can improve batch completion time on your own workload, but every page consumes browser and system resources and increases pressure on target sites. Puppeteer’s official references do not publish an optimal concurrency number. Start with a small configurable limit, measure CPU, memory, failures, and page latency, then adjust.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #3
const fs = require('node:fs/promises');
const path = require('node:path');
const puppeteer = require('puppeteer');
const urls = (await fs.readFile('urls.txt', 'utf8'))
.split(/r?n/).map(x => x.trim()).filter(Boolean);
const concurrency = Number(process.env.CONCURRENCY || 4);
await fs.mkdir('screenshots', { recursive: true });
const browser = await puppeteer.launch({ headless: true });
const results = new Array(urls.length);
let next = 0;
async function worker() {
while (true) {
const index = next++;
if (index >= urls.length) return;
const url = urls[index];
const page = await browser.newPage();
const output = path.join('screenshots', `${String(index).padStart(4, '0')}.webp`);
try {
await page.setViewport({ width: 1365, height: 768, deviceScaleFactor: 1 });
const response = await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
if (response && response.status() >= 400) throw new Error(`HTTP ${response.status()}`);
await page.screenshot({ path: output, type: 'webp', quality: 82, fullPage: true });
results[index] = { url, ok: true, output };
} catch (error) {
results[index] = { url, ok: false, error: error.message };
} finally {
await page.close();
}
}
}
try {
await Promise.all(Array.from({ length: Math.min(concurrency, urls.length) }, worker));
} finally {
await browser.close();
}
await fs.writeFile('results.json', JSON.stringify(results, null, 2));
This pool gives each URL an independent result. The indexed output preserves input order even though workers finish at different times. Keep the finally blocks: a navigation timeout or detached page must not leak a page, and the browser must close after all workers settle.
Screenshot options that matter
| Option | Use | Important qualification |
|---|---|---|
fullPage: true |
Capture the entire document. | Output dimensions and file size can become very large. |
clip |
Capture a specified rectangle. | Use coordinates that exist at the chosen viewport and page state. |
type |
Select PNG, JPEG, or WebP. | Puppeteer documents PNG as the default. |
quality |
Control JPEG/WebP compression. | Range is 0–100 and does not apply to PNG. |
omitBackground: true |
Hide the default white background. | Transparency depends on page content and output support. |
path |
Write the image to disk. | Relative paths resolve from the current working directory; omit it to receive image data instead. |
For one component, locate it and call the element handle’s screenshot method. Puppeteer scrolls the element into view when needed; the call fails if the element has been detached.
const card = await page.waitForSelector('.invoice-card');
await card.screenshot({ path: 'invoice-card.png' });
Naming, determinism, and output safety
- Use an index plus a sanitized hostname or hash so duplicate paths cannot overwrite each other.
- Fix viewport dimensions, device scale factor, format, quality, and full-page policy for comparable images.
- Validate URLs before launching navigation and reject unsupported schemes.
- Write a JSON or CSV manifest containing URL, output path, timestamp, status, and error text.
- Do not treat an HTTP error page as a successful business-page capture; inspect the navigation response when status matters.
Troubleshooting common failures
Navigation timeout
The site may be slow, blocked, or waiting indefinitely for network activity. Increase the timeout only when justified, switch from networkidle2 to domcontentloaded plus a selector, and record the failed URL. Do not retry unlimited times.
Blank or incomplete image
Capture after the actual content marker appears, verify the viewport, and test whether lazy-loaded content requires scrolling. A full-page image can also expose application behavior that differs from the visible viewport.
Recommended Free Tools
High memory or CPU use
Lower the worker limit, close pages promptly, avoid launching one browser per URL, and measure again on the real URL mix. There is no official universal concurrency recommendation.
Detached node error
The page replaced the element before its screenshot completed. Re-query the selector after the final render signal and capture immediately.
Authentication or consent blocks
Provide the required cookies, headers, or login flow only when you are authorized. Consent banners, anti-bot checks, rate limits, and terms vary by site; respect each target’s rules.
System-browser incompatibility
Prefer Puppeteer’s bundled browser. An alternative executable path is not covered by Puppeteer’s compatibility guarantee.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. It removes cookie/consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with X-Page-Verdict and X-Billed headers explaining the result. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for all options, including full-page and element capture, device presets, custom CSS/JavaScript, waits, blocking rules, headers, cookies, geolocation, caching, signed links, webhooks, bulk capture of up to 100 URLs per call, and usage reporting.
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}`);
The free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. Create a free ScreenshotNeo account.
FAQ
Should every URL get a new browser?
No. Reuse one browser and create independent pages, closing each page after its job.
Is networkidle2 always best?
No. Choose the readiness condition that matches the target application, often a known selector.
What concurrency should I use?
There is no documented universal number. Start small and benchmark your actual pages and machine while respecting target-site limits.
Quick 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.




