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()
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)
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')
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'))
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))
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)
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
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))
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)
Installation
Install required packages:
# Synchronous (requests)
pip install requests
# Asynchronous (httpx)
pip install httpx
Environment Setup
.env file:
PAGEBOLT_API_KEY=your_api_key_here
Load in Python:
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.getenv('PAGEBOLT_API_KEY')
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
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:
- ✅
requestslibrary (sync, simple) - ✅
httpxlibrary (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)