← GrabShot Blog

How to Capture Website Screenshots Programmatically: 4 Methods Compared

February 19, 2026 · 8 min read

Need to capture website screenshots from code? Whether you're building a link preview service, monitoring visual changes, or generating thumbnails for a directory, there are several ways to do it. Each comes with different tradeoffs around setup complexity, cost, and reliability.

This guide walks through four practical approaches: using a screenshot API, running Puppeteer, using Playwright, and driving Selenium. We'll compare them with real code so you can pick the right tool for your use case.

Method 1: Screenshot API (Fastest to Ship)

A screenshot API handles the browser infrastructure for you. You send a URL, you get back an image. No headless browser to maintain, no Chrome versions to track, no memory leaks to debug at 3 AM.

curl

curl "https://api.grabshot.dev/v1/screenshot?url=https://example.com&width=1280&height=800&format=png" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o screenshot.png

Node.js

const fetch = require('node-fetch');
const fs = require('fs');

async function captureScreenshot(url) {
  const res = await fetch(
    `https://api.grabshot.dev/v1/screenshot?url=${encodeURIComponent(url)}&width=1280&format=png`,
    { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
  );

  if (!res.ok) throw new Error(`API error: ${res.status}`);

  const buffer = Buffer.from(await res.arrayBuffer());
  fs.writeFileSync('screenshot.png', buffer);
  console.log('Screenshot saved');
}

captureScreenshot('https://example.com');

Python

import requests

def capture_screenshot(url: str, output: str = "screenshot.png"):
    response = requests.get(
        "https://api.grabshot.dev/v1/screenshot",
        params={"url": url, "width": 1280, "format": "png"},
        headers={"Authorization": "Bearer YOUR_API_KEY"},
    )
    response.raise_for_status()

    with open(output, "wb") as f:
        f.write(response.content)
    print(f"Saved to {output}")

capture_screenshot("https://example.com")

When to use this: Production apps where you need reliable screenshots without managing browser infrastructure. Link previews, social cards, monitoring dashboards, directory sites.

Try GrabShot Free

25 screenshots/month on the free plan. No credit card required.

Try the API →

Method 2: Puppeteer (Full Control, Node.js)

Puppeteer gives you a headless Chrome instance you control directly. It's the go-to choice when you need full browser automation alongside screenshots, or when you want to run everything on your own servers.

const puppeteer = require('puppeteer');

async function captureScreenshot(url) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });

  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800 });

  await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
  await page.screenshot({ path: 'screenshot.png', fullPage: false });

  await browser.close();
  console.log('Screenshot saved');
}

captureScreenshot('https://example.com');

Pros: Free, full browser control, can interact with pages before screenshotting (click cookie banners, scroll, wait for animations).

Cons: You manage the Chrome binary and its dependencies. Memory usage can spike. Chromium updates can break things. On a VPS, you need to handle font rendering, locale settings, and --no-sandbox flags.

Common Puppeteer gotchas

Method 3: Playwright (Multi-Browser, Better API)

Playwright is Microsoft's answer to Puppeteer. It supports Chromium, Firefox, and WebKit out of the box, and its API is a bit more ergonomic. If you're starting fresh, Playwright is often the better choice.

Node.js

const { chromium } = require('playwright');

async function captureScreenshot(url) {
  const browser = await chromium.launch();
  const page = await browser.newPage({
    viewport: { width: 1280, height: 800 }
  });

  await page.goto(url, { waitUntil: 'networkidle' });
  await page.screenshot({ path: 'screenshot.png' });

  await browser.close();
}

captureScreenshot('https://example.com');

Python

from playwright.sync_api import sync_playwright

def capture_screenshot(url: str, output: str = "screenshot.png"):
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page(viewport={"width": 1280, "height": 800})
        page.goto(url, wait_until="networkidle")
        page.screenshot(path=output)
        browser.close()

capture_screenshot("https://example.com")

Pros: Multi-browser support, auto-wait for elements, better TypeScript types, built-in test runner.

Cons: Same infrastructure burden as Puppeteer. Downloads all three browser engines by default (~400 MB). Slightly heavier install.

Method 4: Selenium (Legacy, Cross-Language)

Selenium has been around since 2004. It supports every major language and browser. But for screenshot-only use cases, it's the heaviest option with the most moving parts.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def capture_screenshot(url: str, output: str = "screenshot.png"):
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--window-size=1280,800")
    options.add_argument("--no-sandbox")

    driver = webdriver.Chrome(options=options)
    driver.get(url)
    driver.save_screenshot(output)
    driver.quit()

capture_screenshot("https://example.com")

When to use: You're already running Selenium for testing and want to add screenshot capabilities. Otherwise, Puppeteer or Playwright are better choices for new projects.

Comparison Table

FactorScreenshot APIPuppeteerPlaywrightSelenium
Setup timeMinutes30 min30 min1 hour+
InfrastructureNone (hosted)You manageYou manageYou manage
Cost at 10K/mo~$29/moServer costsServer costsServer costs
ReliabilityHigh (SLA)MediumMediumMedium
Full pageYesYesYesLimited
Custom viewportYesYesYesYes
Page interactionLimitedFullFullFull
LanguagesAny (HTTP)Node.jsNode/Python/Java/.NETAll major

Which Method Should You Use?

Here's a quick decision framework:

Tips That Apply to Every Method

Wait for the right moment

Pages with lazy loading, animations, or client-side rendering need time. networkidle is a good default, but sometimes you need to wait for a specific element:

// Puppeteer/Playwright
await page.waitForSelector('.hero-image', { timeout: 5000 });
await page.screenshot({ path: 'screenshot.png' });

Handle cookie banners

Cookie consent popups will appear in your screenshots. Either dismiss them programmatically or use a CSS override:

// Hide common cookie banners via CSS injection
await page.addStyleTag({
  content: '[class*="cookie"], [id*="consent"], .cc-banner { display: none !important; }'
});

Set a realistic viewport

Don't use tiny viewports. Most websites are designed for 1280px+ width. Using 800px will trigger responsive layouts and look odd. For mobile screenshots, use 390px (iPhone 14 width).

Consider full-page vs viewport

Viewport screenshots (what fits on screen) are fast and predictable. Full-page screenshots capture everything but can produce very tall images on content-heavy sites. Choose based on your use case.

Wrapping Up

Capturing website screenshots programmatically is a solved problem in 2026. The real question is how much infrastructure you want to own. For most production use cases, an API saves time and headaches. For local automation and testing, Playwright is the modern standard.

If you're evaluating screenshot APIs, check out GrabShot's docs for the full feature set, including automated OG image generation, device frame mockups, and HTML-to-PDF conversion.

Start Capturing Screenshots

Free plan includes 25 screenshots per month. Upgrade when you're ready.

Get Your API Key →