Taking a screenshot of a website programmatically is one of the most common automation tasks. Whether you're building visual regression tests, generating OG images, archiving web pages, or monitoring competitors — you need a reliable way to capture what a web page looks like.
This guide covers three approaches in three languages: browser automation (Playwright), headless browser libraries, and API-based capture. Each has trade-offs in complexity, reliability, and scalability.
Node.js — Playwright
Playwright is the gold standard for browser automation in Node.js. It supports Chromium, Firefox, and WebKit with a clean async API:
import { chromium } from 'playwright';
async function screenshotUrl(url, outputPath, options = {}) {
const browser = await chromium.launch();
const page = await browser.newPage();
// Set viewport
await page.setViewportSize({
width: options.width ?? 1280,
height: options.height ?? 720,
});
// Navigate and wait for content
await page.goto(url, { waitUntil: 'networkidle' });
// Optional: wait for specific element
if (options.waitFor) {
await page.waitForSelector(options.waitFor, { timeout: 10000 });
}
// Capture screenshot
await page.screenshot({
path: outputPath,
fullPage: options.fullPage ?? false,
type: options.format ?? 'png',
quality: options.format === 'jpeg' ? (options.quality ?? 80) : undefined,
});
await browser.close();
console.log(`Screenshot saved: ${outputPath}`);
}
// Usage
await screenshotUrl('https://example.com', 'screenshot.png', {
fullPage: true,
width: 1440,
height: 900,
});
Advanced: Element Screenshot
// Capture a specific element
async function screenshotElement(url, selector, outputPath) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
const element = await page.$(selector);
if (element) {
await element.screenshot({ path: outputPath });
console.log(`Element screenshot saved: ${outputPath}`);
} else {
console.error(`Selector not found: ${selector}`);
}
await browser.close();
}
await screenshotElement(
'https://example.com',
'.hero-section',
'hero.png'
);
Python — Playwright & Selenium
Python + Playwright
from playwright.sync_api import sync_playwright
def screenshot_url(url, output_path, width=1280, height=720, full_page=False):
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": width, "height": height})
page.goto(url, wait_until="networkidle")
page.screenshot(path=output_path, full_page=full_page)
browser.close()
print(f"Screenshot saved: {output_path}")
# Usage
screenshot_url("https://example.com", "screenshot.png", full_page=True)
Python + Selenium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def screenshot_selenium(url, output_path, width=1280, height=720):
options = Options()
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument(f"--window-size={width},{height}")
driver = webdriver.Chrome(options=options)
driver.get(url)
# Full page screenshot (Selenium 4+)
driver.save_screenshot(output_path)
driver.quit()
print(f"Screenshot saved: {output_path}")
screenshot_selenium("https://example.com", "screenshot.png")
Go — chromedp
package main
import (
"context"
"log"
"os"
"time"
"github.com/chromedp/chromedp"
)
func screenshotURL(url, outputPath string, width, height int64) error {
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.WindowSize(int(width), int(height)),
chromedp.Flag("headless", true),
chromedp.Flag("no-sandbox", true),
)
ctx, cancel := chromedp.NewExecAllocator(
context.Background(), opts...)
defer cancel()
ctx, cancel = chromedp.NewContext(ctx)
defer cancel()
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
var buf []byte
err := chromedp.Run(ctx,
chromedp.Navigate(url),
chromedp.WaitReady("body"),
chromedp.Sleep(2*time.Second),
chromedp.FullScreenshot(&buf, 90),
)
if err != nil {
return err
}
return os.WriteFile(outputPath, buf, 0644)
}
func main() {
err := screenshotURL(
"https://example.com",
"screenshot.png",
1280, 720,
)
if err != nil {
log.Fatal(err)
}
log.Println("Screenshot saved")
}
API Approach — No Browser Required
All the browser-based approaches above require installing Chromium (400MB+), managing processes, and handling crashes. A screenshot API eliminates all that complexity:
Node.js
const response = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: {
'X-Api-Key': 'sk_live_your_key_here',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://example.com',
format: 'png',
width: 1280,
height: 720,
full_page: true,
block_ads: true,
device: 'macbook-pro-16',
}),
});
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync('screenshot.png', buffer);
Python
import requests
response = requests.post(
'https://api.snapapi.pics/v1/screenshot',
headers={'X-Api-Key': 'sk_live_your_key_here'},
json={
'url': 'https://example.com',
'format': 'png',
'full_page': True,
'block_ads': True,
'device': 'iphone-15-pro',
}
)
with open('screenshot.png', 'wb') as f:
f.write(response.content)
Go
payload, _ := json.Marshal(map[string]interface{}{
"url": "https://example.com",
"format": "png",
"full_page": true,
"device": "pixel-8",
})
req, _ := http.NewRequest("POST",
"https://api.snapapi.pics/v1/screenshot",
bytes.NewBuffer(payload))
req.Header.Set("X-Api-Key", "sk_live_your_key_here")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
f, _ := os.Create("screenshot.png")
defer f.Close()
f.ReadFrom(resp.Body)
Approach Comparison
| Factor | Playwright | Selenium | chromedp (Go) | SnapAPI (API) |
|---|---|---|---|---|
| Dependencies | Chromium binary | Chrome + ChromeDriver | Chrome binary | None (HTTP only) |
| Setup time | ~5 min | ~15 min | ~10 min | ~1 min |
| Device emulation | Manual config | Limited | Manual config | 30+ presets |
| Anti-bot bypass | Stealth plugins | Limited | Limited | Built-in |
| Scaling | Manual (browser pools) | Selenium Grid | Manual | Automatic |
| Serverless compatible | Difficult (binary size) | No | No | Yes (HTTP call) |
| Extra features | Full browser control | Full browser control | Full browser control | Scraping, PDF, video, AI |
Screenshot Any Website in One API Call
No Chromium to install. No browser crashes. 30+ device presets. 200 free requests/month.
Get Your Free API Key