How to Capture Website Screenshots Programmatically: 4 Methods Compared
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.
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
- Missing fonts: Install
fonts-liberationandfonts-noto-color-emojion Linux or non-Latin text renders as boxes - Memory leaks: Always call
browser.close()in afinallyblock. A leaked browser process eats 200+ MB - Lazy-loaded content:
networkidle2doesn't guarantee all images loaded. You may need custom wait logic - Cookie consent banners: Will appear in your screenshot unless you dismiss them first
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
| Factor | Screenshot API | Puppeteer | Playwright | Selenium |
|---|---|---|---|---|
| Setup time | Minutes | 30 min | 30 min | 1 hour+ |
| Infrastructure | None (hosted) | You manage | You manage | You manage |
| Cost at 10K/mo | ~$29/mo | Server costs | Server costs | Server costs |
| Reliability | High (SLA) | Medium | Medium | Medium |
| Full page | Yes | Yes | Yes | Limited |
| Custom viewport | Yes | Yes | Yes | Yes |
| Page interaction | Limited | Full | Full | Full |
| Languages | Any (HTTP) | Node.js | Node/Python/Java/.NET | All major |
Which Method Should You Use?
Here's a quick decision framework:
- Building a product that needs screenshots? Use an API. You don't want to debug Chrome crashes when you should be building features. GrabShot's free tier is a good starting point.
- Need to interact with the page first? (Login, dismiss popups, scroll) Use Puppeteer or Playwright locally, or combine page interaction with an API that supports custom JavaScript injection.
- Running at scale (1000+ screenshots/day)? An API handles the scaling. Self-hosted Puppeteer at that volume means managing a browser pool, queue system, and health checks.
- One-off scripts or testing? Playwright is the most modern choice for local automation.
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 →