DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to Take a Screenshot of a Website in Python with requests

How to Take a Screenshot of a Website in Python with requests

You need a screenshot. You're using Python. You probably have requests installed already.

No Selenium. No Puppeteer. One POST call. PNG back.

The Problem: Selenium Screenshots Are Slow

Typical Selenium approach in Python:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
driver.get('https://example.com')
driver.save_screenshot('screenshot.png')
driver.quit()
Enter fullscreen mode Exit fullscreen mode

Issues:

  • Selenium must manage a real browser process
  • Browser startup takes 3-10 seconds per screenshot
  • Requires system Chrome/Firefox installation
  • Memory intensive
  • Flaky timing issues (page not fully loaded, etc.)
  • Can't run in serverless

The Solution: PageBolt API + requests

One library. One POST call. Done.

import requests

response = requests.post(
    'https://api.pagebolt.dev/v1/screenshot',
    headers={'Authorization': f'Bearer {api_key}'},
    json={'url': 'https://example.com'}
)

with open('screenshot.png', 'wb') as f:
    f.write(response.content)
Enter fullscreen mode Exit fullscreen mode

That's it.

Synchronous: requests Library

Simple synchronous screenshot:

import requests
import os

api_key = os.getenv('PAGEBOLT_API_KEY')

def take_screenshot(url, filename='screenshot.png'):
    """Take a screenshot of a URL and save to file."""
    response = requests.post(
        'https://api.pagebolt.dev/v1/screenshot',
        headers={
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        },
        json={
            'url': url,
            'format': 'png',
            'width': 1280,
            'height': 720
        }
    )

    if response.status_code == 401:
        raise ValueError('Invalid API key')
    if response.status_code == 429:
        raise RuntimeError('Rate limit exceeded. Upgrade your plan.')
    if response.status_code != 200:
        raise Exception(f'API error: {response.status_code}')

    with open(filename, 'wb') as f:
        f.write(response.content)

    print(f'✓ Screenshot saved: {filename}')

# Usage
take_screenshot('https://github.com')
take_screenshot('https://example.com', 'example.png')
Enter fullscreen mode Exit fullscreen mode

Asynchronous: httpx

Modern async version using httpx:

import httpx
import asyncio
import os

api_key = os.getenv('PAGEBOLT_API_KEY')

async def take_screenshot_async(url, filename='screenshot.png'):
    """Async screenshot with httpx."""
    async with httpx.AsyncClient() as client:
        response = await client.post(
            'https://api.pagebolt.dev/v1/screenshot',
            headers={'Authorization': f'Bearer {api_key}'},
            json={
                'url': url,
                'format': 'png',
                'width': 1280,
                'height': 720
            }
        )

        if response.status_code != 200:
            raise Exception(f'API error: {response.status_code}')

        with open(filename, 'wb') as f:
            f.write(response.content)

        print(f'✓ Screenshot saved: {filename}')

# Usage
asyncio.run(take_screenshot_async('https://github.com'))
Enter fullscreen mode Exit fullscreen mode

Batch Screenshots

Capture multiple sites in parallel:

import httpx
import asyncio
import os

api_key = os.getenv('PAGEBOLT_API_KEY')

async def batch_screenshots(urls):
    """Capture multiple URLs concurrently."""
    async with httpx.AsyncClient() as client:
        tasks = []

        for i, url in enumerate(urls):
            task = client.post(
                'https://api.pagebolt.dev/v1/screenshot',
                headers={'Authorization': f'Bearer {api_key}'},
                json={'url': url, 'format': 'png'}
            )
            tasks.append(task)

        responses = await asyncio.gather(*tasks)

        for i, response in enumerate(responses):
            if response.status_code == 200:
                with open(f'screenshot-{i}.png', 'wb') as f:
                    f.write(response.content)
                print(f'✓ Saved screenshot-{i}.png')
            else:
                print(f'✗ Failed for {urls[i]}: {response.status_code}')

# Usage
urls = [
    'https://github.com',
    'https://stackoverflow.com',
    'https://example.com'
]
asyncio.run(batch_screenshots(urls))
Enter fullscreen mode Exit fullscreen mode

Device Emulation

Capture on mobile or tablet:

import requests

response = requests.post(
    'https://api.pagebolt.dev/v1/screenshot',
    headers={'Authorization': f'Bearer {api_key}'},
    json={
        'url': 'https://example.com',
        'viewportDevice': 'iphone_14_pro',
        'format': 'png'
    }
)

with open('mobile-screenshot.png', 'wb') as f:
    f.write(response.content)
Enter fullscreen mode Exit fullscreen mode

Available devices: iphone_14_pro, iphone_15_plus, ipad_air, pixel_8, macbook_pro_16, and 20+ others.

Real-World Use Cases

Web Scraping Verification — Confirm what the scraper sees:

def scrape_and_verify(url):
    """Scrape a page and take a screenshot for verification."""
    # Scrape with BeautifulSoup or requests
    page = requests.get(url)

    # Take screenshot to verify page state
    screenshot = requests.post(
        'https://api.pagebolt.dev/v1/screenshot',
        headers={'Authorization': f'Bearer {api_key}'},
        json={'url': url}
    )

    with open(f'verification-{url.replace("/", "-")}.png', 'wb') as f:
        f.write(screenshot.content)

    return page.text
Enter fullscreen mode Exit fullscreen mode

E-commerce Testing — Verify product page rendering:

def test_product_pages(product_urls):
    """Test multiple product pages visually."""
    async def capture_all(urls):
        async with httpx.AsyncClient() as client:
            for url in urls:
                resp = await client.post(
                    'https://api.pagebolt.dev/v1/screenshot',
                    headers={'Authorization': f'Bearer {api_key}'},
                    json={'url': url}
                )

                product_name = url.split('/product/')[-1]
                with open(f'products/{product_name}.png', 'wb') as f:
                    f.write(resp.content)

    asyncio.run(capture_all(product_urls))
Enter fullscreen mode Exit fullscreen mode

Monitoring Changes — Detect when pages change:

import hashlib
import time

def monitor_page(url, interval=3600):
    """Periodically check if a page has changed."""
    previous_hash = None

    while True:
        response = requests.post(
            'https://api.pagebolt.dev/v1/screenshot',
            headers={'Authorization': f'Bearer {api_key}'},
            json={'url': url}
        )

        current_hash = hashlib.md5(response.content).hexdigest()

        if previous_hash and current_hash != previous_hash:
            print(f'🔔 Change detected on {url}')
            # Save new screenshot
            with open(f'changed-{int(time.time())}.png', 'wb') as f:
                f.write(response.content)

        previous_hash = current_hash
        time.sleep(interval)
Enter fullscreen mode Exit fullscreen mode

Installation

Install required packages:

# Synchronous (requests)
pip install requests

# Asynchronous (httpx)
pip install httpx
Enter fullscreen mode Exit fullscreen mode

Environment Setup

.env file:

PAGEBOLT_API_KEY=your_api_key_here
Enter fullscreen mode Exit fullscreen mode

Load in Python:

from dotenv import load_dotenv
import os

load_dotenv()
api_key = os.getenv('PAGEBOLT_API_KEY')
Enter fullscreen mode Exit fullscreen mode

Error Handling

Graceful error handling:

import requests

def safe_screenshot(url, max_retries=3):
    """Take screenshot with retry logic."""
    for attempt in range(max_retries):
        try:
            response = requests.post(
                'https://api.pagebolt.dev/v1/screenshot',
                headers={'Authorization': f'Bearer {api_key}'},
                json={'url': url},
                timeout=30
            )

            if response.status_code == 401:
                raise ValueError('Invalid API key')
            elif response.status_code == 429:
                print('Rate limited. Waiting 60 seconds...')
                import time
                time.sleep(60)
                continue
            elif response.status_code == 200:
                return response.content
            else:
                raise Exception(f'HTTP {response.status_code}')

        except requests.exceptions.Timeout:
            print(f'Timeout on attempt {attempt + 1}/{max_retries}')
            if attempt == max_retries - 1:
                raise
        except requests.exceptions.RequestException as e:
            print(f'Request error: {e}')
            if attempt == max_retries - 1:
                raise

    return None
Enter fullscreen mode Exit fullscreen mode

Pricing

Plan Requests/Month Cost Best For
Free 100 $0 Testing & prototyping
Starter 5,000 $29 Small projects
Growth 25,000 $79 Production apps
Scale 100,000 $199 High-volume automation

Summary

Website screenshots in Python:

  • requests library (sync, simple)
  • httpx library (async, modern)
  • ✅ No browser management
  • ✅ Binary response (response.content)
  • ✅ Device emulation
  • ✅ 100+ requests/month free

Get started: Try PageBolt free — 100 requests/month, no credit card required →

Top comments (0)