ScreenshotNeo

BlogComparisons

Screenshot API vs Headless Browser for Recurring Website Captures

Compare managed screenshot APIs with Playwright for recurring website captures, including runnable code, scheduling, reliability, cost, and how to choose.

By the ScreenshotNeo team4 October 202613 min read

Short answer: Use a screenshot API when your recurring job mostly needs to render URLs with standard capture settings and you want a managed browser endpoint. Use a headless browser such as Playwright when the workflow needs custom interaction, application state, or direct control over browser steps—and your team can operate that infrastructure. Either way, recurring runs need a scheduler, storage, retries, and a way to inspect failures unless your chosen service explicitly provides them.

Both approaches render pages in a browser environment. The main difference is who owns the browser workflow and how much control you need over it. There is no source-grounded universal winner for speed, price, or reliability: compare both against your actual pages and capture volume.

1. What changes between an API and a headless browser?

Concern Screenshot API Headless browser with Playwright
Rendering interface Send an HTTP request with a URL and capture options; receive an image or, for an asynchronous flow, a later result. Write and run code that launches a browser, navigates pages, performs interactions, and captures output.
Browser operations Provider exposes the controls it supports. Check required waits, selectors, state, and output formats. Control the browser steps in your code and can build custom flows around navigation and capture.
Operations The provider operates the rendering endpoint. Your recurring workflow may still need scheduling, retries, storage, and monitoring. You operate the browser runtime and surrounding job system: scheduling, browser dependencies, concurrency, storage, retries, and monitoring.
Best fit Repeated captures of ordinary public pages with common viewport, full-page, selector, or wait requirements. Pages that need a programmable sequence, existing browser automation, or control of steps and state beyond the API’s documented options.

A managed API is not necessarily limited to one URL in and one image out: APIs can expose viewport, selector, wait, script, style, full-page, and asynchronous-delivery options. Conversely, choosing an API does not automatically solve recurrence; asynchronous result delivery is not the same as an interval scheduler.

2. Decide based on the capture job

Choose an API when

  • The job is mostly “capture this URL with these settings.”
  • The provider documents the viewport, wait condition, selector, authentication, and output controls your pages require.
  • You prefer not to deploy and maintain browser binaries and their runtime.
  • You can use a separate scheduler or queue if captures must run on a recurring interval.

Choose Playwright when

  • The capture depends on a custom interaction sequence or state that your code must control.
  • Your team already runs browser automation and can reuse its runtime and operational tooling.
  • You need to implement navigation, application-specific readiness checks, or capture steps directly in code.
  • You can keep the browser, operating system, fonts, settings, and capture environment consistent across runs.

Compare the same requirements for both

  1. State and access: Does the page require login, cookies, headers, a user agent, or geography? Confirm the API’s supported controls, or implement the browser setup yourself.
  2. Readiness: What tells you the page is visually ready—a selector, a particular response, network quiet, or a known delay?
  3. Output: Do you need a viewport screenshot, a full page, one element, or a specific image format?
  4. Repeatability: Can you hold the rendering environment and page state stable enough to make comparisons meaningful?
  5. Operations: Who schedules jobs, stores results, retries transient failures, alerts on missing captures, and retains history?
  6. Volume and cost: What is the expected number of URLs and runs? Price the actual workflow, including infrastructure and engineering time, rather than assuming one approach is universally cheaper.

For an apples-to-apples decision, pilot representative pages at the intended frequency. Include a simple page, a long page with lazy-loaded content, a page with a slow or dynamic section, and any authenticated page that matters. Record successful output, failure categories, manual tuning, operator work, and total cost. This is a measurement plan, not a claim that either path wins.

3. DIY recurring captures with Playwright

Playwright’s screenshot workflow is: launch a browser, open a page, navigate, capture, and close the browser. The example below is a complete one-shot Node.js script. It deliberately does not implement a scheduler; run it from cron, a queue, or your workflow runner at the interval you need.

Install Playwright and its Chromium browser in your project:

npm init -y
npm install playwright
npx playwright install chromium

Save this as capture.mjs and run node capture.mjs https://example.com ./captures/example.png. It creates the output directory, waits for DOM content, captures the full page, and closes the browser even if navigation or capture fails.

import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';

const url = process.argv[2];
const output = process.argv[3] ?? './capture.png';
if (!url) throw new Error('Usage: node capture.mjs <url> [output.png]');

const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({
    viewport: { width: 1440, height: 900 },
    deviceScaleFactor: 1,
  });
  page.setDefaultNavigationTimeout(30_000);
  await page.goto(url, { waitUntil: 'domcontentloaded' });

  // Replace this with a page-specific readiness condition when possible.
  await page.locator('body').waitFor({ state: 'visible', timeout: 10_000 });
  await mkdir(dirname(output), { recursive: true });
  await page.screenshot({ path: output, fullPage: true, animations: 'disabled' });
  console.log(`Saved ${output}`);
} finally {
  await browser.close();
}

For a stable component rather than the whole page, use a locator screenshot:

await page.locator('[data-testid="report"]').screenshot({ path: output });

For a viewport-only image, omit fullPage: true. Playwright supports screenshot output types such as PNG and JPEG; use a matching filename and option, for example await page.screenshot({ path: './capture.jpg', type: 'jpeg', quality: 80 }). Its screenshot tooling also supports element capture and CSS/device scaling controls. See the Playwright screenshots guide and Page screenshot API.

Make the capture recurring

Keep recurrence outside the capture function unless your chosen workflow runner owns it. A cron entry on a system with Node.js and the project installed could run every 15 minutes:

*/15 * * * * cd /srv/site-captures && node capture.mjs https://example.com ./captures/example.png

For a production job, write each run to a unique timestamped path or object key instead of overwriting the prior image. Have the scheduler or queue record the URL, run time, exit status, and output location. Set a maximum runtime and retry only failures that are plausibly transient; otherwise a persistent bad URL can consume every retry slot.

Wait for what makes the page ready

Navigation completion is not necessarily visual readiness. The example uses domcontentloaded and then checks that the body is visible, which is only a generic starting point. A page may still be fetching data or rendering a chart. Prefer a page-specific signal:

await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.locator('[data-testid="dashboard-ready"]').waitFor({ state: 'visible' });

Playwright also offers navigation wait conditions such as load and networkidle. A quiet network can be a poor readiness signal for pages with long polling or background requests. A fixed delay can be useful for a known animation or delayed widget, but it adds latency and may still be too short on a slow run. Choose the condition that corresponds to the content you actually need.

Keep visual comparisons repeatable

Rendering can vary with host operating system, browser version, settings, hardware, power source, and headless mode. For screenshot diffs, capture the baseline and later runs in the same environment. Pin and update browser dependencies deliberately, and use consistent viewport, device scale, locale, timezone, fonts, and page state. A changed environment can create image differences unrelated to a website change. See Playwright’s visual comparison guidance.

4. Managed API request examples

API parameters differ by provider. The examples below show a managed capture request with ScreenshotOne, whose documentation describes capture settings and asynchronous rendering. Check its current parameter names and account requirements in the ScreenshotOne documentation. These examples capture a URL; they do not schedule it to recur.

cURL

curl -G 'https://api.screenshotone.com/take' \
  --data-urlencode 'access_key=YOUR_SCREENSHOTONE_ACCESS_KEY' \
  --data-urlencode 'url=https://example.com' \
  --data-urlencode 'full_page=true' \
  --output capture.png

Python

import requests

response = requests.get(
    'https://api.screenshotone.com/take',
    params={
        'access_key': 'YOUR_SCREENSHOTONE_ACCESS_KEY',
        'url': 'https://example.com',
        'full_page': 'true',
    },
    timeout=90,
)
response.raise_for_status()
with open('capture.png', 'wb') as image:
    image.write(response.content)

Node.js

const params = new URLSearchParams({
  access_key: process.env.SCREENSHOTONE_ACCESS_KEY,
  url: 'https://example.com',
  full_page: 'true',
});

const response = await fetch(`https://api.screenshotone.com/take?${params}`, {
  signal: AbortSignal.timeout(90_000),
});
if (!response.ok) {
  throw new Error(`Screenshot request failed: ${response.status} ${await response.text()}`);
}
const image = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('capture.png', image));

Keep API keys in environment variables or a secret store, not in source control or public client-side code. Treat API timeouts and non-success responses as job outcomes to record. Verify the returned content type and response body before treating an output file as a valid image. For a recurring job, have your scheduler call the request and store each result. If using an asynchronous API mode, arrange callback handling and result storage separately; a webhook delivers a result but does not itself establish a recurring schedule.

5. Full-page captures, lazy content, and dynamic pages

Full-page screenshots are more involved than taking a viewport snapshot. Lazy images may load only after scrolling; sticky headers may repeat or cover content; infinite-scroll pages may never reach a natural end; animations can change between runs. Very long pages also take more time and produce larger files.

  • Lazy content: Test whether the capture flow scrolls the page or otherwise triggers the content before the screenshot. Verify the bottom of the output, not just its top.
  • Sticky elements: Decide whether a repeated header or floating control belongs in the image. If not, hide it with a supported CSS or selector mechanism.
  • Infinite scroll: Define a bounded capture region or a maximum scroll policy. Waiting for the page to finish may never succeed.
  • Animation: Disable animations where the tool supports it, or wait for a stable application-specific state.
  • Selector capture: Confirm the selector matches one intended element and that the element is visible and laid out. A selector existing in the DOM does not prove it is visible.
  • Page-specific tuning: Compare output with the live page and tune waits or full-page strategy. A single generic setting may not work reliably on every page.

ScreenshotOne documents multiple full-page strategies and notes that quality tuning can reduce performance; its guidance also cautions that reliable full-page rendering may not work for every page. Validate difficult pages in a pilot rather than assuming full-page mode guarantees all content is present.

6. Scheduling, retries, storage, and monitoring

A screenshot endpoint answers a capture request. A recurring capture system must also decide when to run, what counts as success, where each artifact goes, and how to recover. Build these pieces whether the renderer is a managed API or your own browser process.

  1. Schedule or enqueue: Use cron for a simple fixed schedule, or a queue/workflow runner when jobs need concurrency limits, backpressure, or centralized run history.
  2. Make runs identifiable: Include the URL or page key and a timestamp in the job record. Use a unique artifact path to preserve history.
  3. Bound the work: Set navigation and request timeouts, cap retries, and limit concurrent browser or API jobs to protect your own worker and avoid overwhelming target sites.
  4. Retry selectively: Retry transient network errors and temporary service failures with a cap and backoff. Do not blindly retry invalid URLs, access-denied pages, or deterministic selector failures.
  5. Check the artifact: Record response status, content type, byte size, and capture metadata. Alert when a scheduled run produces no valid output or repeatedly fails.
  6. Protect data: Screenshots can contain account details or personal information. Restrict artifact access and define retention before capturing sensitive pages.

For asynchronous API flows, the job record should connect the request to the eventual callback or delivered file. Validate callbacks and make result handling idempotent so duplicate delivery does not create duplicate records. The cited ScreenshotOne material documents webhook delivery, including S3 delivery as a use case; it does not establish a built-in recurring interval scheduler.

7. Performance, reliability, and total cost

There is no apples-to-apples benchmark or universal price winner in the available documentation. Measure the pages and schedule you actually need. Include render time, queue or scheduler overhead, failed and retried jobs, artifact transfer and storage, browser-worker maintenance, and engineering time.

Factor What to measure
Latency Time from scheduled start to a validated image, including queue wait, page readiness, capture, and transfer.
Throughput How many URLs your planned concurrency can process without causing timeouts, resource pressure, or target-site load concerns.
Failure recovery Which failures are retried, how long a job can run, and whether missed schedules or late callbacks are visible.
Output quality Whether expected sections, images, and fonts appear, especially on long or dynamic pages.
Cost API charges at expected volume or the full cost of browser compute, storage, operations, and maintenance. Verify current provider pricing directly.

For Playwright, browser and host consistency reduces visual noise but requires maintaining that environment. For a managed API, confirm the documented limits, supported settings, delivery model, and current price for your workload. Do not infer a reliability or speed advantage from the architecture alone.

8. Troubleshooting recurring captures

Symptom Likely cause Fix
Blank or partially rendered image Capture started before the page’s content was ready, or the site returned an interstitial or blocked response. Inspect the page response and rendered state. Wait for a meaningful selector or application signal; confirm access and page availability.
Missing lazy-loaded images The images load only after their region is scrolled into view. Use a full-page strategy that triggers lazy content or explicitly scroll through the relevant sections, then validate the resulting image.
Navigation timeout The page is slow, a resource never settles, or the chosen wait condition is too strict. Inspect which stage hangs. Use a suitable navigation condition and a page-specific readiness check; set a bounded timeout. Avoid relying on network idle for pages with continuous requests.
Selector timeout or missing element The selector is wrong, the element appears later, or the page state differs between runs. Verify the selector against the rendered page, wait for the correct state, and check whether authentication or a locale changes the DOM.
Screenshot diffs show widespread changes Browser, OS, fonts, viewport, device scale, or rendering settings changed. Capture baseline and comparison images in the same environment with consistent settings; update the baseline only after reviewing the change.
Full-page output is clipped or unexpectedly tall Sticky layout, infinite scrolling, or page-specific full-page behavior. Bound the region or scroll behavior, adjust the full-page strategy, and test the page separately. Some pages need specific tuning.
API returns an error or an invalid image file Bad credentials or parameters, a provider error, or an error response saved as though it were an image. Check status and response body before writing the artifact; verify parameter names in current provider docs and log request identifiers without exposing secrets.
Duplicate captures or missing intervals Scheduler overlap, retries, worker restarts, or untracked asynchronous delivery. Give each scheduled run an ID, use idempotent result handling, cap concurrency, and alert on absent results after a deadline.

9. Or skip the browser setup

If your recurring job is URL capture, ScreenshotNeo is a managed screenshot API and MCP server. One GET request returns an image or PDF; use your scheduler to run it on the interval you need. See the ScreenshotNeo API documentation for request options.

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, it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, with no card required.

10. FAQ

Does an asynchronous screenshot API run captures on a schedule?

Not by itself. Asynchronous processing and webhook delivery describe how a requested capture completes; recurrence still needs a scheduler or workflow that submits each run, unless the provider documents scheduling as a feature.

Can I compare screenshots from different rendering environments?

You can compare them, but environment changes can introduce rendering differences unrelated to the site. Keep the environment and settings consistent when the goal is visual change detection.

Should I use a fixed delay for every page?

Only when it matches a known page behavior. A readiness selector or application-specific signal is usually more meaningful; fixed delays can waste time and still be insufficient on slow runs.

Which approach is cheaper?

That depends on volume, provider pricing, browser infrastructure, storage, and the time spent operating the workflow. Estimate both with your real schedule and representative URLs; the available sources do not establish a universal cost winner.

Sources