We use cookies

We use cookies to enhance your experience and analyse site usage.

Back to Blog
Tutorial7 min read

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 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

StatusMeaningAction
200SuccessRead response.content
400Bad URLCheck the url parameter
401Invalid API keyCheck WEBCAPTURE_API_KEY env var
403Quota exceededUpgrade plan or wait for next cycle
429Rate limitedBack off 60 seconds and retry
500Server errorRetry with backoff

Next Steps

Ready to get started?

Create a free account and capture your first screenshot in under 2 minutes.

Get started free