Tutorial Python

How to Capture Website Screenshots with Python

February 10, 2026 · 5 min read

MacBook Pro open to Python development environment

Photo via Unsplash

Need to capture website screenshots from your Python application? Whether you are building a link preview service, monitoring dashboard, or generating thumbnails for a content aggregator, this guide covers everything you need. We will walk through two approaches: using the requests library directly and using the official SnapAPI Python SDK.

Prerequisites: Python 3.8+, a SnapAPI API key (free tier gives you 200 screenshots/month), and pip installed.

Installation

You can interact with SnapAPI using plain HTTP requests or install our SDK for a cleaner interface:

# Option A: Just use requests (no extra dependency)
pip install requests

# Option B: Install the SnapAPI SDK
pip install snapapi

Method 1: Using the requests Library

The most straightforward approach. No SDK needed, just standard HTTP calls.

Basic Screenshot

Capture a webpage and save it as a PNG file:

import requests
import base64

API_KEY = "your_api_key"
API_URL = "https://api.snapapi.pics/v1/screenshot"

def take_screenshot(url: str, output_path: str = "screenshot.png"):
    response = requests.post(
        API_URL,
        headers={
            "X-Api-Key": API_KEY,
            "Content-Type": "application/json"
        },
        json={
            "url": url,
            "format": "png",
            "responseType": "image"
        }
    )
    response.raise_for_status()

    with open(output_path, "wb") as f:
        f.write(response.content)

    print(f"Screenshot saved to {output_path}")

take_screenshot("https://example.com")

Getting Base64 Response

If you want to process the image in memory (e.g., store in a database or pass to another service), use responseType: "json":

import requests
import base64

def take_screenshot_base64(url: str) -> bytes:
    response = requests.post(
        API_URL,
        headers={
            "X-Api-Key": API_KEY,
            "Content-Type": "application/json"
        },
        json={
            "url": url,
            "format": "png",
            "responseType": "json"
        }
    )
    response.raise_for_status()
    data = response.json()

    # Decode base64 to raw image bytes
    image_bytes = base64.b64decode(data["data"])
    return image_bytes

# Use it
img = take_screenshot_base64("https://github.com")
print(f"Image size: {len(img)} bytes")

Method 2: Using the SnapAPI Python SDK

The SDK provides a cleaner, more Pythonic interface with type hints and built-in error handling.

Basic Usage

from snapapi import SnapAPI

client = SnapAPI(api_key="your_api_key")

# Capture a screenshot and save to file
result = client.screenshot(
    url="https://example.com",
    format="png"
)
result.save("screenshot.png")

print(f"Screenshot saved ({result.width}x{result.height})")

Full-Page Capture

Capture the entire page, including content below the fold:

from snapapi import SnapAPI

client = SnapAPI(api_key="your_api_key")

result = client.screenshot(
    url="https://news.ycombinator.com",
    format="png",
    full_page=True
)
result.save("full_page.png")

print(f"Full page: {result.width}x{result.height}")

Mobile Viewport Screenshots

Test responsive designs by setting custom viewport dimensions. Here is how to capture a page at iPhone 15 dimensions:

import requests

# iPhone 15 dimensions
MOBILE_WIDTH = 393
MOBILE_HEIGHT = 852

def capture_mobile(url: str, output: str = "mobile.png"):
    response = requests.post(
        API_URL,
        headers={
            "X-Api-Key": API_KEY,
            "Content-Type": "application/json"
        },
        json={
            "url": url,
            "format": "png",
            "width": MOBILE_WIDTH,
            "height": MOBILE_HEIGHT,
            "responseType": "image"
        }
    )
    response.raise_for_status()

    with open(output, "wb") as f:
        f.write(response.content)

capture_mobile("https://stripe.com")

Or using the SDK with built-in device presets:

from snapapi import SnapAPI

client = SnapAPI(api_key="your_api_key")

# Use a device preset
result = client.screenshot(
    url="https://stripe.com",
    device="iphone-15"
)
result.save("stripe_mobile.png")

Dark Mode Screenshots

Capture websites in dark mode by setting the darkMode parameter. This tells the browser to use prefers-color-scheme: dark:

response = requests.post(
    API_URL,
    headers={
        "X-Api-Key": API_KEY,
        "Content-Type": "application/json"
    },
    json={
        "url": "https://github.com",
        "format": "png",
        "darkMode": True,
        "responseType": "image"
    }
)

with open("github_dark.png", "wb") as f:
    f.write(response.content)

Block Cookie Banners

Nobody wants GDPR consent popups cluttering their screenshots. Enable cookie banner blocking with a single parameter:

result = client.screenshot(
    url="https://bbc.com",
    format="png",
    block_cookie_banners=True
)
result.save("bbc_clean.png")

Batch Processing

Need to capture many pages at once? Here is a production-ready batch processor using concurrent.futures for parallel execution:

import requests
import concurrent.futures
import time
import os

API_KEY = "your_api_key"
API_URL = "https://api.snapapi.pics/v1/screenshot"

def capture_single(url: str, output_dir: str) -> dict:
    """Capture a single URL and return status."""
    filename = url.replace("https://", "").replace("/", "_") + ".png"
    filepath = os.path.join(output_dir, filename)

    try:
        response = requests.post(
            API_URL,
            headers={
                "X-Api-Key": API_KEY,
                "Content-Type": "application/json"
            },
            json={
                "url": url,
                "format": "png",
                "width": 1280,
                "height": 800,
                "blockCookieBanners": True,
                "responseType": "image"
            },
            timeout=30
        )
        response.raise_for_status()

        with open(filepath, "wb") as f:
            f.write(response.content)

        return {"url": url, "status": "ok", "file": filepath}
    except Exception as e:
        return {"url": url, "status": "error", "error": str(e)}


def batch_capture(urls: list, output_dir: str = "screenshots", max_workers: int = 5):
    """Capture multiple URLs in parallel."""
    os.makedirs(output_dir, exist_ok=True)
    results = []

    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(capture_single, url, output_dir): url
            for url in urls
        }

        for future in concurrent.futures.as_completed(futures):
            result = future.result()
            results.append(result)
            status = "OK" if result["status"] == "ok" else "FAIL"
            print(f"  [{status}] {result['url']}")

    succeeded = sum(1 for r in results if r["status"] == "ok")
    print(f"\nDone: {succeeded}/{len(urls)} screenshots captured.")
    return results


# Usage
urls = [
    "https://github.com",
    "https://stripe.com",
    "https://vercel.com",
    "https://linear.app",
    "https://figma.com",
    "https://notion.so",
    "https://tailwindcss.com",
    "https://nextjs.org",
]

batch_capture(urls, output_dir="batch_screenshots", max_workers=4)

Tip: Keep max_workers between 3 and 5 to stay within rate limits. If you are on a paid plan with higher rate limits, you can increase this to 10 or more.

Comparing requests vs SDK

Which approach should you use? Here is a quick comparison:

Both approaches call the same API and produce identical results. The SDK is just a convenience wrapper that saves you 10-15 lines of boilerplate per call.

Error Handling

Production code needs proper error handling. Here is a robust pattern:

import requests
import time

def screenshot_with_retry(url: str, max_retries: int = 3) -> bytes:
    """Take a screenshot with retry logic."""
    for attempt in range(max_retries):
        try:
            response = requests.post(
                API_URL,
                headers={
                    "X-Api-Key": API_KEY,
                    "Content-Type": "application/json"
                },
                json={
                    "url": url,
                    "format": "png",
                    "responseType": "image"
                },
                timeout=30
            )

            if response.status_code == 429:
                # Rate limited: wait and retry
                retry_after = int(response.headers.get("Retry-After", 5))
                print(f"Rate limited, waiting {retry_after}s...")
                time.sleep(retry_after)
                continue

            response.raise_for_status()
            return response.content

        except requests.exceptions.Timeout:
            print(f"Timeout on attempt {attempt + 1}/{max_retries}")
            time.sleep(2 ** attempt)  # Exponential backoff
        except requests.exceptions.HTTPError as e:
            if response.status_code >= 500:
                print(f"Server error, retrying...")
                time.sleep(2 ** attempt)
            else:
                raise  # Client errors (4xx) should not be retried

    raise Exception(f"Failed to capture {url} after {max_retries} retries")

Integration with Django

Here is a quick example of generating screenshots inside a Django view:

# views.py
from django.http import HttpResponse
import requests

SNAPAPI_KEY = "your_api_key"

def capture_view(request):
    url = request.GET.get("url")
    if not url:
        return HttpResponse("Missing url parameter", status=400)

    response = requests.post(
        "https://api.snapapi.pics/v1/screenshot",
        headers={
            "X-Api-Key": SNAPAPI_KEY,
            "Content-Type": "application/json"
        },
        json={
            "url": url,
            "format": "png",
            "width": 1200,
            "height": 630,
            "blockCookieBanners": True,
            "responseType": "image"
        },
        timeout=30
    )

    return HttpResponse(
        response.content,
        content_type="image/png",
        headers={"Cache-Control": "public, max-age=3600"}
    )

Next Steps

Try SnapAPI Free

Get 200 free screenshots per month. No credit card required.

Get Started Free →

Related Reading

Last updated: February 19, 2026