PYTHON GUIDE

Screenshot API with Python: Complete Guide 2026

From basic captures to async batch processing, Django integration, and Celery automation — everything you need to use a screenshot API in Python.

Free API Key — No Card Needed

Why Use a Screenshot API Instead of Selenium or Playwright?

Python developers automating web screenshots face a common dilemma: run Selenium or Playwright yourself, or call an external API? Both work, but the trade-offs are significant.

Running Playwright in your own environment means installing Chromium, managing browser versions, handling headless display servers on Linux (Xvfb), and dealing with memory leaks from long-running browser processes. On serverless platforms like AWS Lambda or Google Cloud Run, this is outright painful — Chromium binary size often exceeds function limits, and cold starts become unacceptably slow.

SnapAPI removes all of that. Your Python code makes an HTTP request and gets back a PNG or JPEG. The browser infrastructure lives on SnapAPI's servers. You pay a tiny per-screenshot fee instead of maintaining browser servers yourself.

Installation and Setup

No library to install — SnapAPI uses standard HTTP. Use requests, httpx, or aiohttp depending on your async preferences.

pip install requests  # or httpx, or aiohttp for async

Basic Screenshot Capture

import requests
import os

SNAP_KEY = os.environ['SNAPAPI_KEY']

def screenshot(url, **kwargs):
    # Capture a screenshot, return PNG bytes
    params = {
        'access_key': SNAP_KEY,
        'url': url,
        'width': 1280,
        'height': 800,
        'format': 'png',
        'full_page': 'false',
        **kwargs
    }
    r = requests.get('https://api.snapapi.pics/screenshot', params=params, timeout=30)
    r.raise_for_status()
    return r.content

# Save to disk
png = screenshot('https://example.com')
with open('example.png', 'wb') as f:
    f.write(png)
print(f'Captured: {len(png)//1024}KB')

Async Batch Capture with aiohttp

When you need to screenshot dozens or hundreds of URLs, async HTTP is far faster than sequential requests. Here is a production-ready async batch screenshotter:

import asyncio
import aiohttp
import os
from pathlib import Path

SNAP_KEY = os.environ['SNAPAPI_KEY']
BASE_URL = 'https://api.snapapi.pics/screenshot'
CONCURRENCY = 5

async def screenshot_async(session, url, out):
    params = {
        'access_key': SNAP_KEY,
        'url': url,
        'width': 1440,
        'height': 900,
        'format': 'png',
    }
    try:
        async with session.get(BASE_URL, params=params, timeout=aiohttp.ClientTimeout(total=45)) as r:
            if r.status == 200:
                out.write_bytes(await r.read())
                return {'url': url, 'ok': True}
            return {'url': url, 'ok': False, 'status': r.status}
    except Exception as e:
        return {'url': url, 'ok': False, 'error': str(e)}

async def batch_screenshot(urls, output_dir='screenshots'):
    Path(output_dir).mkdir(exist_ok=True)
    sem = asyncio.Semaphore(CONCURRENCY)
    results = []

    async def bounded(url, i):
        async with sem:
            slug = url.split('//')[-1].replace('/', '_')[:50]
            out = Path(output_dir) / f'{i:04d}_{slug}.png'
            result = await screenshot_async(session, url, out)
            results.append(result)
            print(f"{'OK' if result['ok'] else 'FAIL'}: {url}")

    async with aiohttp.ClientSession() as session:
        await asyncio.gather(*[bounded(u, i) for i, u in enumerate(urls)])
    return results

urls = ['https://github.com', 'https://stripe.com/pricing', 'https://vercel.com']
results = asyncio.run(batch_screenshot(urls))
ok = sum(1 for r in results if r['ok'])
print(f'Done: {ok}/{len(urls)} succeeded')

Django Integration: Screenshot on Model Save

# models.py
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
import requests, os

class Portfolio(models.Model):
    name = models.CharField(max_length=200)
    url  = models.URLField()
    screenshot = models.ImageField(upload_to='screenshots/', blank=True, null=True)

@receiver(post_save, sender=Portfolio)
def capture_screenshot(sender, instance, created, **kwargs):
    if created or not instance.screenshot:
        from .tasks import refresh_screenshot
        refresh_screenshot.delay(instance.pk)

# tasks.py (Celery)
from celery import shared_task
from django.core.files.base import ContentFile
from django.utils import timezone

@shared_task(bind=True, max_retries=3)
def refresh_screenshot(self, portfolio_pk):
    from .models import Portfolio
    portfolio = Portfolio.objects.get(pk=portfolio_pk)
    try:
        r = requests.get('https://api.snapapi.pics/screenshot', params={
            'access_key': os.environ['SNAPAPI_KEY'],
            'url': portfolio.url,
            'width': 1280, 'height': 800,
            'format': 'jpeg', 'quality': 85,
        }, timeout=45)
        r.raise_for_status()
        filename = f'portfolio_{portfolio_pk}_{int(timezone.now().timestamp())}.jpg'
        portfolio.screenshot.save(filename, ContentFile(r.content), save=True)
    except Exception as exc:
        raise self.retry(exc=exc, countdown=60)

FastAPI with Background Tasks

from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
import httpx, os
from pathlib import Path

app = FastAPI()
SNAP_KEY = os.environ['SNAPAPI_KEY']

class ShotRequest(BaseModel):
    url: str
    width: int = 1280
    height: int = 800
    full_page: bool = False

async def do_capture(url, width, height, full_page, out):
    async with httpx.AsyncClient() as client:
        r = await client.get('https://api.snapapi.pics/screenshot', params={
            'access_key': SNAP_KEY, 'url': url,
            'width': width, 'height': height,
            'full_page': str(full_page).lower(), 'format': 'png',
        }, timeout=45)
        if r.status_code == 200:
            out.write_bytes(r.content)

@app.post('/capture')
async def capture(req: ShotRequest, background_tasks: BackgroundTasks):
    slug = req.url.split('//')[-1].replace('/', '_')[:40]
    out = Path('screenshots') / f'{slug}.png'
    Path('screenshots').mkdir(exist_ok=True)
    background_tasks.add_task(do_capture, req.url, req.width, req.height, req.full_page, out)
    return {'status': 'queued', 'output': str(out)}

Scraping with Python and SnapAPI

Beyond screenshots, SnapAPI exposes a scrape endpoint that returns rendered HTML after JavaScript execution. This is invaluable for Python scraping projects where target sites rely on client-side rendering.

import requests, os
from bs4 import BeautifulSoup

SNAP_KEY = os.environ['SNAPAPI_KEY']

def scrape(url, wait_for=None, delay=0):
    # Returns fully-rendered HTML after JS execution
    params = {
        'access_key': SNAP_KEY,
        'url': url,
        'delay': delay,
    }
    if wait_for:
        params['wait_for_selector'] = wait_for
    r = requests.get('https://api.snapapi.pics/scrape', params=params, timeout=45)
    r.raise_for_status()
    return r.text

# Example: scrape a React SPA product listing
html = scrape('https://example-spa.com/products', wait_for='.product-grid', delay=1000)
soup = BeautifulSoup(html, 'html.parser')
products = soup.select('.product-card')
for p in products:
    print(p.select_one('.name').text, p.select_one('.price').text)

Data Extraction with Python

The extract endpoint takes a URL and a JSON schema, then returns structured data extracted from the page. No XPath, no CSS selectors, no fragile parsing code — just describe what you want and SnapAPI returns it.

import requests, os, json

SNAP_KEY = os.environ['SNAPAPI_KEY']

def extract(url, schema):
    r = requests.post('https://api.snapapi.pics/extract', json={
        'access_key': SNAP_KEY,
        'url': url,
        'schema': schema,
    }, timeout=45)
    r.raise_for_status()
    return r.json()

# Extract structured product data
result = extract('https://example.com/product/123', schema={
    'name': 'string',
    'price': 'number',
    'rating': 'number',
    'reviews_count': 'integer',
    'in_stock': 'boolean',
    'images': ['string'],
})
print(json.dumps(result, indent=2))

Error Handling and Retry Logic

Production Python code needs robust error handling. Here is a retry wrapper with exponential backoff for SnapAPI calls:

import requests, time, os
from functools import wraps

def with_retry(max_retries=3, backoff=2.0):
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return fn(*args, **kwargs)
                except requests.HTTPError as e:
                    if e.response.status_code in (429, 503) and attempt < max_retries - 1:
                        sleep = backoff ** attempt
                        print(f'Rate limited, retrying in {sleep}s...')
                        time.sleep(sleep)
                    else:
                        raise
                except requests.Timeout:
                    if attempt < max_retries - 1:
                        time.sleep(backoff ** attempt)
                    else:
                        raise
        return wrapper
    return decorator

@with_retry(max_retries=3)
def safe_screenshot(url, **kwargs):
    return screenshot(url, **kwargs)

Pricing for Python Projects

SnapAPI pricing scales with your usage. The free tier includes 200 screenshots per month — enough for development and testing. The $19/month plan covers 5,000 screenshots, suitable for most production Python applications. High-volume data pipelines can use the $79/month plan for 50,000 screenshots per month.

No per-seat pricing, no concurrent request limits that block async workflows, and no surprise overage bills — SnapAPI charges only for what you use. Sign up at snapapi.pics/dashboard and get your free API key in under a minute.

Scheduled Screenshot Pipelines with Python and Celery Beat

Monitoring dashboards, competitor tracking systems, and visual regression suites all need scheduled screenshot pipelines. Python + Celery Beat is the gold standard for this in production environments. Here is a complete periodic task setup for SnapAPI-powered screenshot monitoring:

# celery.py
from celery import Celery
from celery.schedules import crontab

app = Celery('monitor', broker='redis://localhost:6379/0')

app.conf.beat_schedule = {
    'screenshot-monitor-hourly': {
        'task': 'tasks.run_screenshot_monitor',
        'schedule': crontab(minute=0),  # every hour
    },
}

# tasks.py
from celery import shared_task
import requests, os, hashlib
from pathlib import Path
from datetime import datetime

SNAP_KEY = os.environ['SNAPAPI_KEY']
MONITOR_URLS = [
    'https://myapp.com/',
    'https://myapp.com/pricing',
    'https://myapp.com/dashboard',
]

@shared_task
def run_screenshot_monitor():
    timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M')
    results = []

    for url in MONITOR_URLS:
        slug = hashlib.md5(url.encode()).hexdigest()[:8]
        r = requests.get('https://api.snapapi.pics/screenshot', params={
            'access_key': SNAP_KEY,
            'url': url,
            'width': 1440,
            'height': 900,
            'format': 'png',
        }, timeout=45)

        if r.status_code == 200:
            path = Path(f'screenshots/{slug}/{timestamp}.png')
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_bytes(r.content)
            results.append({'url': url, 'path': str(path), 'ok': True})
        else:
            results.append({'url': url, 'ok': False, 'status': r.status_code})

    return results

Comparing Screenshots with Pillow and NumPy

Capturing screenshots is only half the job — you also need to compare them. Python's Pillow and NumPy libraries make per-pixel comparison straightforward:

from PIL import Image, ImageChops
import numpy as np
from pathlib import Path

def compare_screenshots(path_a: Path, path_b: Path, threshold: float = 0.005) -> dict:
    img_a = Image.open(path_a).convert('RGB')
    img_b = Image.open(path_b).convert('RGB')

    # Resize to same dimensions if needed
    if img_a.size != img_b.size:
        img_b = img_b.resize(img_a.size, Image.LANCZOS)

    arr_a = np.array(img_a, dtype=np.float32)
    arr_b = np.array(img_b, dtype=np.float32)

    diff = np.abs(arr_a - arr_b)
    changed_pixels = np.sum(np.any(diff > 10, axis=2))  # >10 per channel
    total_pixels = arr_a.shape[0] * arr_a.shape[1]
    change_ratio = changed_pixels / total_pixels

    # Save diff image
    diff_img = ImageChops.difference(img_a, img_b)
    diff_path = path_b.parent / f'diff_{path_b.name}'
    diff_img.save(diff_path)

    return {
        'changed_pixels': int(changed_pixels),
        'total_pixels': total_pixels,
        'change_ratio': float(change_ratio),
        'exceeds_threshold': change_ratio > threshold,
        'diff_path': str(diff_path),
    }

# Example usage in monitoring pipeline
result = compare_screenshots(
    Path('screenshots/abc123/20260403_1200.png'),
    Path('screenshots/abc123/20260403_1300.png'),
    threshold=0.005
)
if result['exceeds_threshold']:
    print(f"VISUAL CHANGE DETECTED: {result['change_ratio']:.2%} of pixels changed")
    # Send Slack alert, email, etc.

Generating PDF Reports with Screenshots

Marketing agencies and SaaS analytics platforms often need PDF reports that include screenshots of client dashboards or web pages. SnapAPI captures the screenshot; Python's reportlab or weasyprint turns it into a polished PDF report.

from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Image as RLImage, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
import requests, os, io

SNAP_KEY = os.environ['SNAPAPI_KEY']

def build_screenshot_report(urls: list, output_pdf: str):
    story = []
    styles = getSampleStyleSheet()

    for url in urls:
        r = requests.get('https://api.snapapi.pics/screenshot', params={
            'access_key': SNAP_KEY,
            'url': url,
            'width': 1280,
            'height': 800,
            'format': 'jpeg',
            'quality': 90,
        }, timeout=45)

        if r.status_code == 200:
            img_data = io.BytesIO(r.content)
            rl_img = RLImage(img_data, width=480, height=300)
            story.append(Paragraph(url, styles['Heading3']))
            story.append(rl_img)

    doc = SimpleDocTemplate(output_pdf, pagesize=A4)
    doc.build(story)
    print(f'Report saved: {output_pdf}')

build_screenshot_report(
    ['https://example.com', 'https://example.com/pricing'],
    'client_report.pdf'
)

Environment Variables and Security Best Practices

Never hardcode your SnapAPI key in Python source files. Always load it from environment variables. In development, use python-dotenv to load from a .env file. In production, use your hosting platform's secret management: AWS Secrets Manager, Google Secret Manager, Heroku Config Vars, Fly.io secrets, or Kubernetes Secrets.

# Development: .env file
# SNAPAPI_KEY=sk_live_your_key_here

from dotenv import load_dotenv
load_dotenv()

import os
SNAP_KEY = os.environ['SNAPAPI_KEY']  # KeyError if missing -- intentional

# Or with a fallback for testing:
SNAP_KEY = os.environ.get('SNAPAPI_KEY', 'sk_test_placeholder')

Rotate your API key immediately if it is ever exposed in logs, error messages, or version control. SnapAPI dashboard lets you revoke and regenerate keys instantly without downtime.

Start Building Today

Sign up at snapapi.pics/dashboard for 200 free screenshots per month. No credit card required, API key available instantly. The Python examples in this guide are production-ready — copy them into your project and ship your first screenshot workflow today.