Recommended Free Tools
To capture several URLs in parallel with Browserless, send one authenticated POST /screenshot request per URL and run the requests concurrently. Each response contains image bytes for that page, so save every response to its own file. In JavaScript, Promise.all works for small batches; for larger batches, use a bounded worker pool and keep concurrency within the level supported by your Browserless plan.
What you need and how the requests work
- A Browserless API token and the endpoint appropriate for your account or region. Browserless’s documented example endpoint is
https://production-sfo.browserless.io/screenshot?token=YOUR_API_TOKEN_HERE; use the endpoint assigned to your account if it differs. See Browserless’s screenshot REST API guide. - A list of page URLs, plus a distinct output filename for each result.
- An account plan that supports the number of concurrent sessions you intend to run. Browserless does not document one universal maximum for every plan; check the concurrent sessions guide.
Each URL gets its own POST request and browser session. The request body contains a url and, optionally, screenshot settings in an options object. The REST response is image data rather than JSON, so read it as bytes and write it directly to disk. Browserless documents the independent-session behavior in its concurrent sessions guide.
As an Amazon Associate I earn from qualifying purchases.
Capture multiple URLs in parallel with JavaScript
This Node.js example uses built-in fetch and fs/promises. Set your token in the environment as BROWSERLESS_TOKEN; it sends one request per URL, checks HTTP status, and saves each PNG using its list position.
import { writeFile } from 'node:fs/promises';
const token = process.env.BROWSERLESS_TOKEN;
if (!token) throw new Error('Set BROWSERLESS_TOKEN before running this script.');
const urls = [
'https://example.com',
'https://developer.mozilla.org',
'https://www.wikipedia.org'
];
const endpoint = `https://production-sfo.browserless.io/screenshot?token=${encodeURIComponent(token)}`;
await Promise.all(urls.map(async (url, index) => {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url,
options: { type: 'png', fullPage: true }
})
});
if (!response.ok) {
const detail = await response.text();
throw new Error(`Capture ${index + 1} failed (${response.status}): ${detail}`);
}
const image = Buffer.from(await response.arrayBuffer());
await writeFile(`screenshot-${index + 1}.png`, image);
console.log(`Saved screenshot-${index + 1}.png for ${url}`);
}));
Run it with BROWSERLESS_TOKEN=your_token node capture.mjs. The .mjs extension enables ES modules in Node.js. If a URL fails, Promise.all rejects; successful requests may already have written their files. For a production batch where one failure should not discard the overall result, use Promise.allSettled and record each URL’s outcome.
#1 Best Overall
Use bounded concurrency for larger lists
Promise.all(urls.map(...)) starts every request without a concurrency limit. That is convenient for a handful of pages, but a large list can exceed your plan’s supported sessions, increase pressure on your own process, or make retries harder to control. Use a worker pool when the list is larger: workers take the next URL only when they finish the current capture. Choose a limit your plan supports rather than assuming a universal Browserless cap.
import { writeFile } from 'node:fs/promises';
const token = process.env.BROWSERLESS_TOKEN;
const urls = [
'https://example.com',
'https://developer.mozilla.org',
'https://www.wikipedia.org'
];
const concurrency = 2; // Set to a level supported by your Browserless plan.
const endpoint = `https://production-sfo.browserless.io/screenshot?token=${encodeURIComponent(token)}`;
let next = 0;
async function worker() {
while (true) {
const index = next++;
if (index >= urls.length) return;
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: urls[index],
options: { type: 'png', fullPage: true }
})
});
if (!response.ok) {
const detail = await response.text();
throw new Error(`Capture ${index + 1} failed (${response.status}): ${detail}`);
}
await writeFile(`screenshot-${index + 1}.png`, Buffer.from(await response.arrayBuffer()));
}
}
await Promise.all(Array.from(
{ length: Math.min(concurrency, urls.length) },
() => worker()
));
This minimal pool stops if a worker throws. If the batch must continue after individual failures, catch errors inside the worker, save the URL and error to a results list, then report failures after all workers finish. For retry logic, retry only transient failures and cap the number of attempts so a persistently blocked page does not loop indefinitely.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Choose capture settings for each page
Put the target URL in url. Common screenshot choices go in options; Browserless documents the available REST controls in its screenshot API reference.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
- Format: set
options.typeto a supported format such aspng,jpeg, orwebp. Make the filename extension match the selected format. - Full page or viewport: use
options.fullPage: trueto include content beyond the viewport. Without it, the capture is generally limited to the visible viewport. - Element capture: provide
selectorat the top level of the request, alongsideurl, rather than insideoptions. - Viewport and scale: set viewport dimensions and device scale factor when you need consistent dimensions or higher-density output.
- Clip and quality: use a clip rectangle for a specific region; configure quality where applicable to the chosen output format.
- Wait conditions: configure waits for events, selectors, functions, or a timeout when a page needs time to render.
- Lazy-loaded content: use
scrollPage: trueto trigger content loading before capture; combine with full-page capture if the whole long page should appear.
Because requests are independent, each URL can have its own body and settings. For example, a dashboard might need a selector capture while an article needs full-page mode.
Rank #3
Keep outputs, failures, and runtime manageable
Associate every image with its URL
Index-based filenames are simple and avoid collisions, but keep a manifest mapping each index or filename to the original URL. If filenames are derived from URLs, sanitize them: URLs may contain characters unsuitable for filesystem paths, and two different URLs can reduce to the same filename. Never let parallel tasks write to the same path.
Inspect completion instead of assuming the page is correct
A successful HTTP response means the endpoint returned an image; it does not prove the intended page content was captured. Browserless documents blank or white captures, CAPTCHA screens, access-denied or 403 pages, and missing elements as possible outcomes. Review images when correctness matters, and add an appropriate wait condition if content is absent or incomplete.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
Plan for timeouts and retries
Parallel work can reduce the time spent waiting on independent page loads, but no speedup or fixed throughput is guaranteed: page rendering, network conditions, waits, image size, and plan concurrency all affect elapsed time. For reliability, log the input URL, response status, elapsed time, and output path; retry only failures that appear transient. A timeout or blocked page may need a different wait or may not be capturable through the same route.
Account for image size and memory
Full-page images can be substantially larger than viewport captures, especially at high device scale factors. Writing each response to disk as it arrives avoids retaining the entire batch in memory. If your chosen runtime buffers responses before writing, keep the batch size and concurrency modest or stream data where supported.
Best Value
Troubleshooting Browserless parallel captures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| 401 or another authorization error | The token is missing, invalid, or attached incorrectly. | Check that the request URL includes the token query parameter and that the token belongs to the intended Browserless account. |
| Request fails when many captures start together | The batch may exceed plan-supported concurrency or encounter transient capacity/network errors. | Reduce the worker-pool limit, confirm your plan’s concurrent-session support, and retry transient failures selectively. |
| Image is blank or white | The page may not have finished rendering, may require interaction, or may be blocked. | Add a wait for a relevant selector or event, use an appropriate timeout, and inspect whether the page shows a CAPTCHA or access-denied response. |
| Page content is missing from a full-page image | Content may load only after scrolling or after a delay. | Try scrollPage: true, full-page mode, and a wait suited to the page’s loading behavior. |
| Selector capture misses the target | The selector may not exist when capture begins, or it may be located in the wrong part of the request. | Wait for the selector and provide selector at the request’s top level alongside url. |
| Files have the wrong extension or cannot be opened | The extension may not match the returned image format, or a failed response may have been saved as an image. | Check response.ok before writing bytes and align the filename extension with options.type. |
| Some files are missing after a batch error | Promise.all rejects when one request rejects, though other requests may have completed. |
Use Promise.allSettled or per-task error handling to capture every result and report partial failures explicitly. |
Or skip the browser setup
ScreenshotNeo provides a screenshot API and MCP server. Its API can capture a URL with one GET request; cookie banners and consent overlays, newsletter popups, and chat widgets are removed before capture, with each step configurable. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server includes screenshot, page-info, and PDF tools for AI agents.
For example, save a WebP capture from a URL with cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
See the ScreenshotNeo API documentation for request options. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Sign up for ScreenshotNeo’s free plan.
Frequently Asked Questions
Can I use a different programming language to submit the parallel requests?
Yes. The required pattern is one POST request per URL, run concurrently, with each image response saved separately. Browserless documents examples using Python ThreadPoolExecutor, Java CompletableFuture, and .NET Task.WhenAll in its concurrent sessions guide.
Does Browserless guarantee that a successful screenshot contains the page I wanted?
No. An image response can depict a CAPTCHA, access-denied page, blank output, or incomplete content. Check the capture itself when the result matters.
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.




