How to Automate Website Screenshots at Scale
Automate website screenshot capture with batch processing, scheduling, and CI/CD integration. Code examples for Node.js, Python, and shell scripts.
Last updated: 2026-03-25
Try ScreenshotAPI free
5 free credits. No credit card required.
Automating website screenshots is essential for monitoring, archiving, competitive analysis, and generating thumbnails at scale. Instead of capturing one page at a time, you can build automated pipelines that process hundreds or thousands of URLs. This guide covers batch capture, scheduling, and integration patterns using ScreenshotAPI.
Batch Capture with Node.js
Sequential processing
javascriptimport fs from 'fs'; const API_KEY = process.env.SCREENSHOT_API_KEY; const urls = [ 'https://github.com', 'https://stripe.com', 'https://linear.app', 'https://vercel.com', 'https://tailwindcss.com', ]; async function captureScreenshot(url) { const params = new URLSearchParams({ url, width: '1440', height: '900', type: 'png', waitUntil: 'networkidle' }); const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': API_KEY } } ); if (!response.ok) throw new Error(`Failed: ${url} (${response.status})`); return Buffer.from(await response.arrayBuffer()); } for (const url of urls) { const domain = new URL(url).hostname.replace(/\./g, '_'); const buffer = await captureScreenshot(url); fs.writeFileSync(`screenshots/${domain}.png`, buffer); console.log(`Captured: ${url}`); }
Concurrent processing with rate limiting
javascriptasync function captureAll(urls, concurrency = 10) { const results = []; const queue = [...urls]; async function worker() { while (queue.length > 0) { const url = queue.shift(); try { const buffer = await captureScreenshot(url); const domain = new URL(url).hostname.replace(/\./g, '_'); const timestamp = new Date().toISOString().split('T')[0]; const filename = `screenshots/${domain}_${timestamp}.png`; fs.writeFileSync(filename, buffer); results.push({ url, filename, status: 'success' }); console.log(`OK: ${url}`); } catch (error) { results.push({ url, status: 'error', error: error.message }); console.error(`FAIL: ${url} - ${error.message}`); } } } const workers = Array.from({ length: concurrency }, () => worker()); await Promise.all(workers); return results; } const results = await captureAll(urls, 10); console.log(`Completed: ${results.filter(r => r.status === 'success').length}/${results.length}`);
Batch Capture with Python
Concurrent with asyncio
pythonimport asyncio import aiohttp import os from urllib.parse import urlparse from datetime import date API_KEY = os.environ["SCREENSHOT_API_KEY"] BASE_URL = "https://screenshotapi.to/api/v1/screenshot" async def capture(session, url): params = { "url": url, "width": 1440, "height": 900, "type": "png", "waitUntil": "networkidle", } headers = {"x-api-key": API_KEY} async with session.get(BASE_URL, params=params, headers=headers) as response: if response.status != 200: print(f"FAIL: {url} ({response.status})") return None data = await response.read() domain = urlparse(url).hostname.replace(".", "_") filename = f"screenshots/{domain}_{date.today()}.png" with open(filename, "wb") as f: f.write(data) print(f"OK: {url}") return filename async def capture_all(urls, concurrency=10): semaphore = asyncio.Semaphore(concurrency) async with aiohttp.ClientSession() as session: async def limited_capture(url): async with semaphore: return await capture(session, url) results = await asyncio.gather(*[limited_capture(url) for url in urls]) return results urls = [ "https://github.com", "https://stripe.com", "https://linear.app", "https://vercel.com", "https://tailwindcss.com", ] os.makedirs("screenshots", exist_ok=True) asyncio.run(capture_all(urls))
Batch Capture with Shell Scripts
Simple loop
bash#!/bin/bash API_KEY="sk_live_your_api_key" OUTPUT_DIR="screenshots" mkdir -p "$OUTPUT_DIR" while IFS= read -r url; do domain=$(echo "$url" | sed 's|https://||' | sed 's|[/.]|_|g') timestamp=$(date +%Y-%m-%d) curl -s -G "https://screenshotapi.to/api/v1/screenshot" \ -d "url=$url" \ -d "width=1440" \ -d "height=900" \ -d "type=png" \ -H "x-api-key: $API_KEY" \ --output "$OUTPUT_DIR/${domain}_${timestamp}.png" echo "Captured: $url" done < urls.txt
Parallel with xargs
bashcat urls.txt | xargs -P 10 -I {} bash -c ' domain=$(echo "{}" | sed "s|https://||" | sed "s|[/.]|_|g") curl -s -G "https://screenshotapi.to/api/v1/screenshot" \ -d "url={}" \ -d "width=1440" \ -d "height=900" \ -d "type=png" \ -H "x-api-key: '$API_KEY'" \ --output "screenshots/${domain}.png" echo "Done: {}" '
Scheduling with Cron
Add a cron entry to capture screenshots daily:
bash# Capture screenshots every day at 6 AM 0 6 * * * /path/to/capture_screenshots.sh >> /var/log/screenshots.log 2>&1
CI/CD Integration
GitHub Actions
yamlname: Capture Screenshots on: schedule: - cron: '0 6 * * 1' # Every Monday at 6 AM workflow_dispatch: jobs: capture: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Capture screenshots run: | mkdir -p screenshots while IFS= read -r url; do domain=$(echo "$url" | sed 's|https://||' | sed 's|[/.]|_|g') curl -s -G "https://screenshotapi.to/api/v1/screenshot" \ -d "url=$url" \ -d "width=1440" \ -d "height=900" \ -d "type=png" \ -H "x-api-key: ${{ secrets.SCREENSHOT_API_KEY }}" \ --output "screenshots/${domain}.png" done < urls.txt - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: screenshots-${{ github.run_number }} path: screenshots/
Uploading to S3
javascriptimport { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; const s3 = new S3Client({ region: 'us-east-1' }); async function captureAndUpload(url) { const buffer = await captureScreenshot(url); const domain = new URL(url).hostname; const timestamp = new Date().toISOString().split('T')[0]; const key = `screenshots/${domain}/${timestamp}.png`; await s3.send(new PutObjectCommand({ Bucket: 'my-screenshots-bucket', Key: key, Body: buffer, ContentType: 'image/png', CacheControl: 'public, max-age=86400' })); return `https://my-screenshots-bucket.s3.amazonaws.com/${key}`; }
Monitoring and Change Detection
Compare screenshots over time to detect visual changes:
javascriptimport { createHash } from 'crypto'; async function detectChanges(url, previousHash) { const buffer = await captureScreenshot(url); const currentHash = createHash('sha256').update(buffer).digest('hex'); if (previousHash && currentHash !== previousHash) { console.log(`Change detected on ${url}`); return { changed: true, hash: currentHash, image: buffer }; } return { changed: false, hash: currentHash }; }
Handling Failures
Build retry logic for reliability:
javascriptasync function captureWithRetry(url, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await captureScreenshot(url); } catch (error) { console.warn(`Attempt ${attempt}/${maxRetries} failed for ${url}: ${error.message}`); if (attempt < maxRetries) { await new Promise(r => setTimeout(r, 1000 * attempt)); } } } throw new Error(`Failed after ${maxRetries} attempts: ${url}`); }
Next Steps
- Set up visual regression testing with automated screenshot comparison
- Learn about generating website thumbnails for link directories
- Read the API documentation for all parameters
- Check pricing for volume-appropriate credit packages
Frequently asked questions
How do I take screenshots of multiple URLs at once?
Use a script that loops through your URL list and calls ScreenshotAPI for each one. Process them concurrently with Promise.all in JavaScript, asyncio.gather in Python, or xargs in shell scripts.
Can I schedule automatic screenshots?
Yes. Use cron jobs, GitHub Actions scheduled workflows, or cloud functions with a timer trigger to capture screenshots on a regular schedule.
How many screenshots can I capture per minute?
ScreenshotAPI handles concurrent requests without rate limiting issues for standard plans. For large batches (1000+ URLs), process in groups of 10-20 concurrent requests for optimal throughput.
How do I store automated screenshots?
Save to cloud storage like S3, Google Cloud Storage, or Cloudflare R2. Include the URL, timestamp, and viewport size in the filename for easy retrieval.
Related resources
Start capturing screenshots today
Create a free account and get 5 credits to try the API. No credit card required. Pay only for what you use.