How to Capture Website Screenshots with Python
February 10, 2026 · 5 min read
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:
- Use
requestsif you want zero dependencies beyond the standard library, need full control over HTTP details, or are integrating into an existing codebase that already uses requests. - Use the SDK if you want type hints and autocompletion, built-in retry logic, device presets, and a cleaner API. The SDK also handles base64 decoding and error parsing for you.
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
- Full API Documentation -- all parameters, response formats, and error codes
- Interactive Playground -- test the API in your browser
- Generate OG Images -- use screenshots to create social sharing cards
- Visual Regression Testing -- automate screenshot comparison in CI/CD
Related Reading
- Screenshot API with Node.js — Node.js integration guide
- Full API Documentation — all parameters and options
- Free Screenshot API — 200 free captures per month, no credit card
- Generate OG Images — create social sharing cards with Python
- Screenshot API Pricing Guide 2026 — compare costs at scale
- ScreenshotOne Alternative — why SnapAPI beats ScreenshotOne on price and features