DEV Community

Cover image for 3 Ways to Capture Website Screenshots Programmatically
Dennis
Dennis

Posted on

3 Ways to Capture Website Screenshots Programmatically

There are three main ways to take a website screenshot programmatically: headless browser libraries (Puppeteer, Playwright), screenshot APIs, and OS-level CLI tools. Headless browsers give you full control but require managing Chrome instances. APIs handle rendering for you via HTTP. CLI tools work for simple static pages but choke on JavaScript-heavy sites.

Each approach fits different situations. I've used all three in production, and the right choice depends on your scale, budget, and how much infrastructure you want to own. Here's a practical breakdown.

Method 1: Headless Browser Libraries

Headless browsers are the most popular approach. You launch a real browser (Chrome, Firefox, WebKit) without a visible window, navigate to a URL, and call a screenshot method. The browser renders the page exactly like a user would see it, JavaScript and all.

The three main libraries:

  • Puppeteer (Node.js) controls Chrome/Chromium. Maintained by the Chrome team.
  • Playwright (Node.js, Python, Java, .NET) controls Chrome, Firefox, and WebKit. Built by Microsoft.
  • Selenium (most languages) controls any browser through WebDriver. The oldest option, originally built for testing.

Puppeteer Example (Node.js)

const puppeteer = require("puppeteer");

async function captureScreenshot(url, outputPath) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setViewport({ width: 1280, height: 720 });
  await page.goto(url, { waitUntil: "networkidle0" });
  await page.screenshot({ path: outputPath, fullPage: true });

  await browser.close();
}

captureScreenshot("https://example.com", "screenshot.png");
Enter fullscreen mode Exit fullscreen mode

This downloads Chromium on first run (~170MB), launches it, navigates to the page, waits for network activity to settle, and takes a screenshot. Around 10-15 lines for the basic case.

Playwright Example (Python)

from playwright.sync_api import sync_playwright

def capture_screenshot(url: str, output_path: str):
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page(viewport={"width": 1280, "height": 720})

        page.goto(url, wait_until="networkidle")
        page.screenshot(path=output_path, full_page=True)

        browser.close()

capture_screenshot("https://example.com", "screenshot.png")
Enter fullscreen mode Exit fullscreen mode

Playwright's Python API is clean and supports all three browser engines. The sync_playwright context manager handles setup and teardown.

When Headless Browsers Work Well

Headless browsers shine when you need fine-grained control over the page before capturing. You can:

  • Log in to authenticated pages
  • Click buttons, fill forms, dismiss modals
  • Inject custom CSS or JavaScript
  • Wait for specific elements to appear
  • Intercept network requests

They're also free to run. No per-screenshot costs beyond your compute bill.

Where They Get Painful

The pain starts at scale. Each browser instance eats 100-300MB of RAM. Running 10 concurrent screenshots needs 1-3GB just for Chrome processes. You also need to handle:

  • Browser crashes and zombie processes
  • Font rendering differences across OS environments
  • Timeout handling for slow or broken pages
  • Cookie banners, popups, and consent dialogs
  • Memory leaks from pages that never finish loading

I've written about Puppeteer memory issues in detail. It's solvable, but it takes real engineering time.

Method 2: Screenshot APIs

Screenshot APIs abstract away the browser entirely. You send an HTTP request with a URL and parameters, and you get back an image. No browser to install, no processes to manage, no infrastructure to maintain.

How They Work

Every screenshot API runs a fleet of headless browsers behind the scenes. They've already solved the hard problems: browser pooling, crash recovery, font libraries, timeout handling, caching. You pay per screenshot instead of paying for server time.

curl Example

curl -G "https://app.snap-render.com/v1/screenshot" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d "url=https://example.com" \
  -d "format=png" \
  -d "width=1280" \
  -d "height=720" \
  --output screenshot.png
Enter fullscreen mode Exit fullscreen mode

One request. No dependencies. Works from any language that can make HTTP calls.

Node.js Example

const https = require("https");
const fs = require("fs");

const params = new URLSearchParams({
  url: "https://example.com",
  format: "png",
  width: "1280",
  height: "720",
  full_page: "true",
});

const options = {
  hostname: "app.snap-render.com",
  path: `/v1/screenshot?${params}`,
  headers: { "X-API-Key": "YOUR_API_KEY" },
};

https.get(options, (res) => {
  const chunks = [];
  res.on("data", (chunk) => chunks.push(chunk));
  res.on("end", () => fs.writeFileSync("screenshot.png", Buffer.concat(chunks)));
});
Enter fullscreen mode Exit fullscreen mode

Or with an SDK, it's even shorter. Most screenshot APIs (SnapRender, ScreenshotOne, Urlbox, and others) provide client libraries for popular languages.

The Tradeoffs

APIs are the fastest path to working screenshots. Sign up, get a key, make a request. The downside is cost: you're paying per screenshot. At low volume (hundreds per month) the free tiers cover you. At tens of thousands per month, you're looking at $29-199/month depending on the provider.

You also give up some control. Most APIs expose common options (viewport size, full page, format, wait conditions), but if you need to interact with a page in complex ways (multi-step form submissions, custom JavaScript execution), you might hit limits.

Method 3: OS-Level and CLI Tools

For simple cases, you don't need a full browser library or an API. Several CLI tools can render HTML to images.

Chrome Headless CLI

Chrome itself can take screenshots from the command line:

google-chrome --headless --screenshot=output.png \
  --window-size=1280,720 \
  --disable-gpu \
  https://example.com
Enter fullscreen mode Exit fullscreen mode

This uses the same rendering engine as Puppeteer but without a Node.js wrapper. Good for shell scripts and cron jobs.

wkhtmltoimage

wkhtmltoimage --width 1280 --quality 80 https://example.com screenshot.png
Enter fullscreen mode Exit fullscreen mode

wkhtmltoimage uses an older version of WebKit. It's fast and lightweight, but its JavaScript support is limited. Pages that rely on modern JS frameworks (React, Vue, Svelte) often render incorrectly or as blank screens.

Python with subprocess

import subprocess

def capture_with_chrome(url: str, output_path: str):
    subprocess.run([
        "google-chrome",
        "--headless",
        "--screenshot=" + output_path,
        "--window-size=1280,720",
        "--disable-gpu",
        "--no-sandbox",
        url,
    ], check=True)

capture_with_chrome("https://example.com", "screenshot.png")
Enter fullscreen mode Exit fullscreen mode

When CLI Tools Make Sense

CLI tools are best for:

  • One-off scripts where you already have Chrome installed
  • Static HTML pages (invoices, reports, emails)
  • Environments where you can't install Node.js or Python packages
  • Batch processing with simple shell scripts

They're not suitable for anything that requires waiting for dynamic content, handling authentication, or processing JavaScript-heavy pages reliably.

Comparison Table

Factor Headless Browser Screenshot API CLI Tool
Setup time 10-30 minutes 2-5 minutes 5-10 minutes
Lines of code 10-50 3-10 1-5
JavaScript rendering Full Full Limited/None
Rendering quality Excellent Excellent Varies
Maintenance burden High (updates, crashes, memory) None Low
Cost at 100/month ~$0 (your server) $0 (free tiers) ~$0
Cost at 10K/month $20-100/month (server) $29-79/month $10-50/month (server)
Cost at 100K/month $200-1000/month + engineering $79-199/month Not practical
Concurrent screenshots Limited by RAM Handled by API Usually single-threaded
Scalability Manual (add servers, manage pools) Automatic Poor
Page interaction Full (click, type, scroll) Limited (varies by API) None
Error handling You build it Built-in Minimal
Caching You build it Usually built-in You build it

Which Approach to Use

The decision tree is simpler than most articles make it:

Are you prototyping or building a personal project?
Use a headless browser. Puppeteer if you're in Node.js, Playwright if you want Python or cross-browser support. You'll learn the most, and the scale constraints won't matter.

Are you building a product feature that needs screenshots in production?
Start with an API. You can switch to self-hosted later if costs don't make sense, but you'll ship weeks faster. The engineering time to productionize a headless browser setup is real, and that time has a cost too.

Are you capturing static HTML or simple pages from a script?
Use Chrome's headless CLI. One command, no dependencies beyond Chrome, and it handles most straightforward cases.

Do you need to interact with pages in complex ways (login flows, multi-step forms)?
Use a headless browser library. APIs generally can't handle arbitrary page interactions, though some support cookie injection and basic element clicks.

Are you processing screenshots at very high volume (100K+/month)?
Run the numbers. At high volume, self-hosting headless browsers can be cheaper, but you need engineering capacity to keep it running. If your team is small, the API cost might be less than the engineering cost of maintaining the infrastructure.

A Note on Hybrid Approaches

Nothing stops you from using multiple methods. I've seen teams use an API for their main product flow (where reliability matters most) and a local Puppeteer setup for internal tools and testing. The screenshot code is usually isolated enough that swapping implementations later isn't a major refactor.

The important thing is picking the approach that matches your current constraints. You can always migrate when your requirements change. If you're taking a website screenshot programmatically for the first time, start with whatever gets you a working result fastest, then optimize from there.

Quick Reference

Puppeteer (Node.js): npm install puppeteer then page.screenshot()
Playwright (Python): pip install playwright && playwright install then page.screenshot()
Screenshot API: GET https://api.example.com/screenshot?url=...
Chrome CLI: google-chrome --headless --screenshot=output.png URL

Each path to taking a website screenshot programmatically has its place. The best tool is the one that matches where you are right now, not where you think you'll be in a year.

Top comments (0)