Tutorial March 8, 2026 10 min read

How to Automate Web Screenshots in Python

Python is the go-to language for automation, and taking web screenshots is one of the most common automation tasks developers face. Whether you are monitoring a competitor's pricing page, archiving web content for compliance, generating thumbnail images, or building automated reports, Python gives you the tools to do it efficiently.

This tutorial walks through automating web screenshots using the SnapAPI Python SDK. We will start with basic captures and build up to batch processing, scheduled monitoring, and production-ready error handling.

Prerequisites

You need Python 3.8+ and a SnapAPI API key. The free tier gives you 200 requests per month, which is more than enough to follow along with this tutorial.

# Install the SnapAPI Python SDK
pip install snapapi

# Or with poetry
poetry add snapapi

Sign up at snapapi.pics to get your API key. You will find it in your dashboard after registration.

Your First Screenshot

Let us start with the simplest possible example: capturing a single URL and saving it as a PNG file.

from snapapi import SnapAPI

# Initialize the client
client = SnapAPI("YOUR_API_KEY")

# Take a screenshot
image_data = client.screenshot(
    url="https://news.ycombinator.com",
    format="png"
)

# Save to disk
with open("hackernews.png", "wb") as f:
    f.write(image_data)

print("Screenshot saved!")

That is the entire script. The SDK handles authentication, HTTP requests, error handling, and binary response parsing. The screenshot() method returns raw bytes that you can write to a file, upload to S3, or pass to an image processing library like Pillow.

Configuring Screenshot Options

The basic screenshot captures a 1280x720 viewport. For most use cases, you will want to customize the output. Here are the options that matter most.

Viewport Size and Device Emulation

# Desktop - wide viewport
desktop = client.screenshot(
    url="https://example.com",
    width=1920,
    height=1080,
    format="png"
)

# Mobile - emulate iPhone 15
mobile = client.screenshot(
    url="https://example.com",
    device="iphone-15",
    format="png"
)

# Tablet
tablet = client.screenshot(
    url="https://example.com",
    width=768,
    height=1024,
    format="png"
)

Full Page Capture

By default, only the visible viewport is captured. For long pages, enable full-page mode to scroll and capture everything:

full = client.screenshot(
    url="https://en.wikipedia.org/wiki/Python_(programming_language)",
    full_page=True,
    format="png"
)
# Warning: full-page screenshots of very long pages can be large (10MB+)

Output Format and Quality

# JPEG with quality control (smaller files)
jpeg = client.screenshot(
    url="https://example.com",
    format="jpeg",
    quality=80  # 1-100, lower = smaller file
)

# WebP (good compression, wide browser support)
webp = client.screenshot(
    url="https://example.com",
    format="webp",
    quality=85
)

# AVIF (best compression, 30-50% smaller than WebP)
avif = client.screenshot(
    url="https://example.com",
    format="avif",
    quality=80
)

Blocking Ads and Cookie Banners

# Clean screenshots without visual noise
clean = client.screenshot(
    url="https://example.com",
    block_ads=True,
    block_cookie_banners=True,
    format="png"
)

Batch Screenshot Capture

The real power of automation comes when you need to capture many URLs. Here is a pattern for batch processing with rate limiting and error handling.

import time
import os
from pathlib import Path
from snapapi import SnapAPI

client = SnapAPI("YOUR_API_KEY")

urls = [
    "https://github.com",
    "https://stackoverflow.com",
    "https://dev.to",
    "https://news.ycombinator.com",
    "https://reddit.com/r/programming",
    "https://lobste.rs",
    "https://hackernoon.com",
]

output_dir = Path("screenshots")
output_dir.mkdir(exist_ok=True)

results = {"success": 0, "failed": 0, "errors": []}

for url in urls:
    # Generate a safe filename from the URL
    slug = url.split("//")[1].replace("/", "_").rstrip("_")
    filepath = output_dir / f"{slug}.png"

    try:
        image = client.screenshot(
            url=url,
            format="png",
            width=1280,
            height=720,
            block_ads=True,
            block_cookie_banners=True
        )

        filepath.write_bytes(image)
        results["success"] += 1
        print(f"  Captured: {url} -> {filepath}")

    except Exception as e:
        results["failed"] += 1
        results["errors"].append({"url": url, "error": str(e)})
        print(f"  Failed: {url} - {e}")

    # Be respectful with rate limits
    time.sleep(0.5)

print(f"
Done: {results['success']} captured, {results['failed']} failed")

Concurrent Capture with asyncio

For larger batches, sequential processing is slow. If your plan supports it, you can capture multiple screenshots concurrently. Here is an async pattern using aiohttp alongside the SnapAPI REST endpoint directly:

import asyncio
import aiohttp
from pathlib import Path

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.snapapi.pics/v1/screenshot"

async def capture_one(session, url, output_path):
    """Capture a single screenshot asynchronously."""
    params = {
        "url": url,
        "format": "webp",
        "width": 1280,
        "height": 720,
        "block_ads": "true",
    }
    headers = {"Authorization": f"Bearer {API_KEY}"}

    try:
        async with session.get(BASE_URL, params=params, headers=headers) as resp:
            if resp.status == 200:
                data = await resp.read()
                Path(output_path).write_bytes(data)
                return {"url": url, "status": "ok", "size": len(data)}
            else:
                text = await resp.text()
                return {"url": url, "status": "error", "code": resp.status, "msg": text}
    except Exception as e:
        return {"url": url, "status": "error", "msg": str(e)}

async def capture_batch(urls, concurrency=5):
    """Capture multiple URLs with controlled concurrency."""
    sem = asyncio.Semaphore(concurrency)
    output_dir = Path("screenshots")
    output_dir.mkdir(exist_ok=True)

    async def bounded(url):
        async with sem:
            slug = url.split("//")[1].replace("/", "_").rstrip("_")
            return await capture_one(session, url, output_dir / f"{slug}.webp")

    async with aiohttp.ClientSession() as session:
        tasks = [bounded(url) for url in urls]
        return await asyncio.gather(*tasks)

# Usage
urls = ["https://github.com", "https://dev.to", "https://lobste.rs"]
results = asyncio.run(capture_batch(urls, concurrency=3))
for r in results:
    print(r)
Rate limit note: SnapAPI enforces rate limits per plan. The free tier allows 10 requests per minute. Starter allows 60/min, and Pro allows 300/min. Adjust your concurrency accordingly.

Scheduled Monitoring

One of the most practical applications is monitoring websites on a schedule. Capture daily screenshots of competitor pricing pages, your own production site, or regulatory pages you need to track.

import schedule
import time
from datetime import datetime
from pathlib import Path
from snapapi import SnapAPI

client = SnapAPI("YOUR_API_KEY")

MONITOR_URLS = [
    {"url": "https://your-app.com", "name": "production"},
    {"url": "https://competitor.com/pricing", "name": "competitor-pricing"},
]

def capture_monitors():
    """Take timestamped screenshots of all monitored URLs."""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    base_dir = Path("monitoring") / timestamp
    base_dir.mkdir(parents=True, exist_ok=True)

    for target in MONITOR_URLS:
        try:
            image = client.screenshot(
                url=target["url"],
                format="png",
                width=1440,
                height=900,
                full_page=True,
                block_ads=True,
                block_cookie_banners=True
            )

            filepath = base_dir / f"{target['name']}.png"
            filepath.write_bytes(image)
            print(f"[{timestamp}] Captured {target['name']}")

        except Exception as e:
            print(f"[{timestamp}] Failed {target['name']}: {e}")

# Run every 6 hours
schedule.every(6).hours.do(capture_monitors)

# Or for testing, run every 5 minutes
# schedule.every(5).minutes.do(capture_monitors)

print("Monitoring started. Press Ctrl+C to stop.")
capture_monitors()  # Run once immediately

while True:
    schedule.run_pending()
    time.sleep(60)

Generating PDF Reports

SnapAPI can also generate PDFs from HTML. This is useful for automated reporting:

# Generate a PDF from a URL
pdf_data = client.screenshot(
    url="https://your-app.com/reports/monthly",
    format="pdf",
    full_page=True,
    width=1200
)

with open("monthly_report.pdf", "wb") as f:
    f.write(pdf_data)

# Or from raw HTML
html_content = """
<html>
<body style="font-family: sans-serif; padding: 2rem;">
  <h1>Monthly Report - March 2026</h1>
  <p>Revenue: $45,000</p>
  <p>New users: 1,200</p>
</body>
</html>
"""

pdf_from_html = client.screenshot(
    html=html_content,
    format="pdf"
)

with open("report_from_html.pdf", "wb") as f:
    f.write(pdf_from_html)

Common Gotchas and Solutions

1. Dynamic Content Not Rendering

Single-page applications (React, Vue, Angular) load content asynchronously. If your screenshot is blank or shows a loading spinner, add a delay or wait for a specific element:

# Wait for content to load
image = client.screenshot(
    url="https://spa-app.com",
    delay=3000,  # Wait 3 seconds after page load
    format="png"
)

# Or wait for a specific CSS selector
image = client.screenshot(
    url="https://spa-app.com",
    wait_for_selector=".main-content",
    format="png"
)

2. Authentication-Required Pages

For pages behind a login, pass cookies or use custom JavaScript to authenticate:

# Pass session cookies
image = client.screenshot(
    url="https://app.example.com/dashboard",
    cookies=[
        {"name": "session_id", "value": "abc123", "domain": "app.example.com"}
    ],
    format="png"
)

3. Large File Sizes

Full-page PNG screenshots can be 10MB+. Use WebP or AVIF to reduce file sizes dramatically without visible quality loss:

# AVIF: typically 70-80% smaller than PNG
image = client.screenshot(
    url="https://example.com",
    format="avif",
    quality=80,
    full_page=True
)
# A 10MB PNG might be only 1-2MB as AVIF

4. Handling Timeouts

Some pages load slowly. Configure timeouts to avoid hanging:

try:
    image = client.screenshot(
        url="https://slow-site.com",
        timeout=30000,  # 30 seconds max
        format="png"
    )
except TimeoutError:
    print("Page took too long to load")
    # Fall back to a placeholder image or retry

Uploading Screenshots to S3

In production, you often want to store screenshots in cloud storage rather than the local filesystem:

import boto3
from snapapi import SnapAPI

s3 = boto3.client("s3")
client = SnapAPI("YOUR_API_KEY")

def capture_and_upload(url, s3_key, bucket="my-screenshots"):
    """Capture a screenshot and upload it to S3."""
    image = client.screenshot(
        url=url,
        format="webp",
        quality=85,
        block_ads=True
    )

    s3.put_object(
        Bucket=bucket,
        Key=s3_key,
        Body=image,
        ContentType="image/webp",
        CacheControl="max-age=86400"  # Cache for 24 hours
    )

    return f"https://{bucket}.s3.amazonaws.com/{s3_key}"

# Usage
public_url = capture_and_upload(
    "https://example.com",
    "captures/example-com/2026-03-08.webp"
)
print(f"Uploaded to: {public_url}")

Conclusion

Python combined with SnapAPI makes web screenshot automation straightforward. You can go from a basic single-URL capture to a production monitoring system in under an hour. The key patterns to remember:

For more on the cost benefits of using an API vs. managing your own browser infrastructure, read our cost analysis.

Try SnapAPI for free

200 free screenshots per month. Install the Python SDK and start capturing in 2 minutes.

Get Your Free API Key