ScreenshotNeo

BlogHow-to

How to Capture Screenshots of Multiple URLs with BrowserCat

Capture a list of URLs with BrowserCat and Playwright, with reliable waits, per-page error handling, bounded concurrency, and clear cost guidance.

By the ScreenshotNeo team4 October 20267 min read

To capture screenshots of multiple URLs with BrowserCat, connect Playwright to BrowserCat’s hosted Chromium endpoint, navigate to each URL, wait for the page condition that suits the site, and save a screenshot. The documented BrowserCat flow is for a page at a time; a URL loop is an implementation pattern, not a verified BrowserCat bulk-screenshot endpoint. See the BrowserCat quick start.

1. Set up BrowserCat and Playwright

Create a BrowserCat account and API key, then install playwright-core. The remote connection uses wss://api.browsercat.com/connect and sends the key in an Api-Key header. Keep the key in an environment variable rather than putting it in source code.

npm install playwright-core

Set the key in your shell before running the script:

export BROWSERCAT_API_KEY="your_api_key"

2. Capture a list sequentially

This Node.js example is a complete starter pattern. It writes one full-page PNG per URL, continues after an individual navigation or capture error, and closes the remote browser session even if the loop fails.

import * as pw from 'playwright-core';
import { mkdir } from 'node:fs/promises';

const urls = [
  'https://example.com/',
  'https://example.org/',
];

async function captureAll() {
  if (!process.env.BROWSERCAT_API_KEY) {
    throw new Error('Set BROWSERCAT_API_KEY before running this script');
  }

  await mkdir('screenshots', { recursive: true });
  const browser = await pw.chromium.connect(
    'wss://api.browsercat.com/connect',
    { headers: { 'Api-Key': process.env.BROWSERCAT_API_KEY } },
  );

  try {
    const context = await browser.newContext();
    const page = await context.newPage();

    for (const [index, url] of urls.entries()) {
      try {
        await page.goto(url, { waitUntil: 'load', timeout: 30_000 });
        await page.screenshot({
          path: `screenshots/page-${index + 1}.png`,
          fullPage: true,
        });
        console.log(`Saved screenshot for ${url}`);
      } catch (error) {
        console.error(`Screenshot failed for ${url}:`, error);
      }
    }

    await context.close();
  } finally {
    await browser.close();
  }
}

captureAll().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

The loop and output names are choices for this example. The BrowserCat documentation establishes the connection and single-page navigation and screenshot operations; adapt paths and error handling to your project.

Choose a readiness condition

waitUntil: 'load' waits for the page load event. It does not guarantee that a single-page application has finished rendering data or that lazy content is ready. When a meaningful element indicates that the page is ready, wait for it explicitly:

await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
await page.locator('main h1').waitFor({ state: 'visible', timeout: 10_000 });
await page.screenshot({ path: outputPath, fullPage: true });

Use a fixed delay only when the site provides no better readiness signal; it adds time without guaranteeing that content has loaded. BrowserCat’s quick start demonstrates page loading but does not prescribe one universal wait policy for all websites.

Capture the viewport instead of the full page

For a viewport screenshot, omit fullPage: true:

await page.screenshot({ path: outputPath, type: 'png' });

Full-page capture is useful for archiving or visual review of long pages, but very long pages can produce large images and take longer to capture. Pick the output size and capture mode that match how the images will be used.

3. Add concurrency only when needed

Sequential processing is a good starting point: it is easy to follow, uses one page at a time, and isolates each URL’s failure. For higher throughput, run a bounded number of workers rather than launching a task for every URL simultaneously.

BrowserCat’s pricing page displays a concurrency figure for its plans, but that headline is not a recommended worker count or a guarantee for a particular script. Validate your chosen limit against your account, target sites, and runtime. More parallel pages also use more browser resources and can increase load on sites.

async function runWithLimit(items, limit, task) {
  let nextIndex = 0;
  const workers = Array.from(
    { length: Math.min(limit, items.length) },
    async () => {
      while (true) {
        const index = nextIndex++;
        if (index >= items.length) return;
        await task(items[index], index);
      }
    },
  );
  await Promise.all(workers);
}

// Example use: captureUrl should create and close its own page per task.
await runWithLimit(urls, 3, async (url, index) => {
  await captureUrl(url, `screenshots/page-${index + 1}.png`);
});

Keep the limit configurable. Start low, record failures and elapsed time, then adjust based on observed results and account limits. Ensure each concurrent task has its own page; do not navigate one shared page from multiple workers.

4. Use cURL, Python, or Node.js for the BrowserCat API

BrowserCat’s documented screenshot workflow in the research uses Playwright over its hosted browser connection. These cURL and Python examples are not BrowserCat screenshot endpoints. They use BrowserCat through a WebSocket browser protocol and therefore require a compatible client; use Playwright when following the documented path. There is no verified BrowserCat bulk screenshot REST endpoint to call once with a URL list.

For the documented method, Node.js with playwright-core is shown above. In Python, the analogous implementation requires a Playwright client that can connect to the documented remote endpoint and supply the API key header; check the current BrowserCat documentation for supported client details before adapting it. Avoid guessing an HTTP route or treating another provider’s endpoint as BrowserCat’s.

5. Track outcomes and make batch runs recoverable

  • Keep the input URL list and output names deterministic so a failed item can be retried.
  • Log each URL, its output path, and the error for failed navigation or capture.
  • For larger jobs, write a status record as each URL completes rather than keeping all results only in memory.
  • Decide whether a failed URL should stop the batch. The example continues; stricter workflows can collect failures and exit nonzero after processing all URLs.
  • Normalize or validate input URLs before processing, and avoid using a URL as a raw filename.

BrowserCat’s pricing page describes soft and hard usage limits. Requests may be rejected at a hard limit until the billing period resets or limits are increased, so monitor usage and handle failed work explicitly.

6. Understand waits, credits, and cost

BrowserCat prices usage with credits, and its pricing page describes different accounting for WebSocket activity and successful Utility API requests. The research snapshot listed Hobby at $0 per month with 1,000 credits and $0.0025 per extra credit, and Business at $50 per month with extra credits at $0.00175. It described WebSocket activity as one credit per 30 seconds, rounded up, and successful Utility API requests as one credit each. These vendor-published figures can change; check the current BrowserCat pricing page before estimating or purchasing.

For a WebSocket batch, session duration matters: a slow readiness condition or a long list can keep the session active longer. Faster waits may reduce session duration, but capturing before content is ready produces incomplete screenshots. Choose readiness based on the page, reuse the session for the batch as shown, and close it in a finally block.

7. Troubleshoot common failures

Symptom Likely cause What to do
Connection fails before navigation Missing or invalid API key, incorrect endpoint, or connection issue. Confirm BROWSERCAT_API_KEY is set, verify the Api-Key header and endpoint against the official quick start, and retry with a small batch.
Navigation times out The site is slow, keeps connections open, or does not reach the chosen load event. Use a timeout suitable for the target. Consider domcontentloaded plus a site-specific selector when that better indicates usable content.
Screenshot is blank or missing content The app rendered after the load event, content is behind a delayed request, or the expected element never appeared. Wait for a visible application element, inspect the page condition, and record which URLs fail. A fixed delay can help diagnose timing but is not a robust readiness signal.
Only the visible viewport appears The screenshot call omitted full-page capture. Set fullPage: true when the complete page is required.
Some output files are absent An individual navigation or screenshot threw and the batch continued. Use the logged URL to retry that item and retain a failure manifest for large jobs.
Requests are rejected during a large run The account may have reached a hard usage limit or the chosen concurrency may exceed practical constraints. Check current usage and plan limits, reduce concurrency, and resume failed URLs after capacity is available.
Parallel captures overwrite or interfere Workers share a page or output path. Give each task its own page and a unique filename derived from its stable input index or identifier.

8. Or skip the browser setup

If you need screenshots for a URL list without managing a hosted browser session, ScreenshotNeo provides a screenshot API: send one GET request per URL. The API accepts the parameter names used by other screenshot APIs, which can make switching straightforward. 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
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}`);

Run one request for each URL in your own loop. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for free.

FAQ

Does BrowserCat have a bulk screenshot endpoint?

The reviewed BrowserCat material documents individual page capture. This guide applies that operation repeatedly in a client-side loop; it does not claim a bulk endpoint.

Can I use the same browser session for every URL?

Yes. The sequential example reuses one page and navigates it to each URL. Concurrent workers should use separate pages.

Should I use load or networkidle?

Use a condition that matches the site’s rendering behavior. A site-specific visible element often gives a more useful signal than waiting for all network activity to stop.

Can I capture an element instead of the whole page?

Playwright supports element screenshots; select the relevant locator and call its screenshot method. BrowserCat’s MCP screenshot action also documents a CSS-selected element, with width and height inputs. The MCP action is a separate interface from the Playwright loop.

Can this workflow produce a PDF?

The code here saves PNG images. Use a PDF-specific workflow if the deliverable is a PDF; do not assume the screenshot loop automatically creates one.