How to Take Website Screenshots with Python Using a Screenshot API
A complete guide to capturing website screenshots in Python with the WebCaptureAPI REST API. Covers requests, async httpx, saving to disk, S3 upload, Django/Flask integration, and error handling.
Why a Screenshot API Instead of Selenium?
Selenium and Playwright are the traditional Python tools for browser automation, but they're heavy:
- A Chrome or Firefox binary must be installed on every machine
- Each browser instance uses ~300 MB of RAM
- They're slow to start and hard to scale horizontally
- Headless browser versions break with dependency updates
A screenshot API like WebCaptureAPI handles all of this on the server side. Your Python code makes a single HTTP request — no browser, no driver, no system dependencies.
Setup
The only requirement is the requests library (already installed in most Python environments):
pip install requests
Get your API key by signing up free — 100 screenshots per month, no credit card.
Basic Screenshot
import os
import requests
def screenshot(url: str, kwargs) -> bytes:
response = requests.get(
'https://api.webcaptureapi.com/api/screenshot',
params={'url': url, kwargs},
headers={'x-api-key': os.environ['WEBCAPTURE_API_KEY']},
timeout=30,
)
response.raise_for_status()
return response.content
# Save to disk
image = screenshot('https://example.com', format='png', width=1280)
with open('screenshot.png', 'wb') as f:
f.write(image)
Full-Page Screenshots
To capture the entire scrollable page, not just the visible viewport:
image = screenshot(
'https://example.com',
format='png',
width=1440,
full_page='true',
)
with open('full-page.png', 'wb') as f:
f.write(image)
Full-page captures are useful for visual testing, archiving, and generating thumbnails of long-form content.
JPEG Output with Quality Control
For smaller file sizes, switch to JPEG and control compression quality:
image = screenshot(
'https://example.com',
format='jpeg',
quality=85, # 1–100, default 90
width=1280,
)
JPEG at quality 85 is typically 60–80% smaller than PNG for photographic content. Use PNG when you need transparency or pixel-exact fidelity.
Capturing JavaScript-Heavy Pages
For React, Vue, or Angular apps that render asynchronously, add a delay to wait for the JS to finish:
image = screenshot(
'https://app.example.com/dashboard',
format='png',
width=1440,
delay=2000, # milliseconds, max 10000
)
Start at 1000–2000ms. If the page still shows loading spinners, increase the delay.
Async Version with httpx
For async frameworks (FastAPI, async Django), use httpx:
pip install httpx
import os
import httpx
async def screenshot_async(url: str, kwargs) -> bytes:
async with httpx.AsyncClient(timeout=30) as client:
response = await client.get(
'https://api.webcaptureapi.com/api/screenshot',
params={'url': url, kwargs},
headers={'x-api-key': os.environ['WEBCAPTURE_API_KEY']},
)
response.raise_for_status()
return response.content
# In an async context:
image = await screenshot_async('https://example.com', format='png')
Batch Screenshots
To capture multiple URLs, use asyncio.gather for concurrency:
import asyncio
async def batch_screenshots(urls: list[str], concurrency: int = 5) -> list[bytes]:
semaphore = asyncio.Semaphore(concurrency)
async def capture(url):
async with semaphore:
return await screenshot_async(url, format='jpeg', quality=85)
return await asyncio.gather(*[capture(url) for url in urls])
images = asyncio.run(batch_screenshots(['https://example.com', 'https://python.org']))
Uploading to S3
import boto3
def screenshot_to_s3(url: str, bucket: str, key: str) -> str:
image = screenshot(url, format='jpeg', quality=85)
s3 = boto3.client('s3')
s3.put_object(
Bucket=bucket,
Key=key,
Body=image,
ContentType='image/jpeg',
)
return f's3://{bucket}/{key}'
path = screenshot_to_s3('https://example.com', 'my-bucket', 'screenshots/example.jpg')
Django View Integration
# views.py
import os
import requests
from django.http import HttpResponse, JsonResponse
def capture_view(request):
url = request.GET.get('url')
if not url:
return JsonResponse({'error': 'url parameter required'}, status=400)
try:
response = requests.get(
'https://api.webcaptureapi.com/api/screenshot',
params={'url': url, 'format': 'png', 'width': '1280'},
headers={'x-api-key': os.environ['WEBCAPTURE_API_KEY']},
timeout=30,
)
response.raise_for_status()
return HttpResponse(response.content, content_type='image/png')
except requests.HTTPError as e:
return JsonResponse({'error': str(e)}, status=502)
Error Handling
from requests.exceptions import HTTPError, Timeout
import time
def screenshot_with_retry(url: str, max_retries: int = 3, kwargs) -> bytes:
for attempt in range(1, max_retries + 1):
try:
response = requests.get(
'https://api.webcaptureapi.com/api/screenshot',
params={'url': url, kwargs},
headers={'x-api-key': os.environ['WEBCAPTURE_API_KEY']},
timeout=30,
)
if response.status_code in (429, 500, 502, 503):
if attempt < max_retries:
time.sleep(attempt * 2)
continue
response.raise_for_status()
return response.content
except Timeout:
if attempt == max_retries:
raise
time.sleep(attempt)
Error Codes Reference
| Status | Meaning | Action |
|---|---|---|
200 | Success | Read response.content |
400 | Bad URL | Check the url parameter |
401 | Invalid API key | Check WEBCAPTURE_API_KEY env var |
403 | Quota exceeded | Upgrade plan or wait for next cycle |
429 | Rate limited | Back off 60 seconds and retry |
500 | Server error | Retry with backoff |
Next Steps
- Node.js screenshot API guide — same patterns in JavaScript
- Scheduled screenshots — automate recurring captures without any polling code
- Full API reference — all parameters and response formats
Ready to get started?
Create a free account and capture your first screenshot in under 2 minutes.
Get started free