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)
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:
- Use the SDK for simple integrations, raw HTTP for async/concurrent workloads
- Always handle errors and implement retries for production code
- Use WebP or AVIF format to minimize storage costs
- Implement caching to avoid redundant API calls
- Respect rate limits with concurrency controls
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