Django Screenshot API: Capture Web Pages from Your Python Backend
You're building a Django application. At some point, you need to capture a screenshot or generate a PDF:
- Your users want to preview their work before publishing
- You need to generate report PDFs from HTML templates
- You're building a monitoring tool that needs page snapshots
- You need social preview images for shared content
- You're capturing pages for analysis or archival
You google "Django screenshot" and find options:
- Selenium — heavyweight, slow, requires browser binary
- Puppeteer (via PyPuppeteer) — possible but adds Node.js dependency to Python project
- wkhtmltopdf — limited CSS support, requires system binary
- Build it yourself — maintenance nightmare
There's a simpler way: a screenshot API.
The Django Problem: Screenshots Without Extra Dependencies
Django is excellent at building web applications, but screenshot/PDF generation requires external tools. Here's why the common options fall short:
Option 1: Selenium (Heavyweight)
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
def capture_screenshot(url):
# Setup headless Chrome
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
driver = webdriver.Chrome(options=options)
driver.get(url)
# Wait for content to load
WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.TAG_NAME, "body"))
)
time.sleep(2) # Additional wait for JS rendering
screenshot = driver.save_screenshot('screenshot.png')
driver.quit()
return screenshot
# Problems:
# - Slow initialization (~2-5 seconds per screenshot)
# - Requires Chrome binary + ChromeDriver
# - Memory-intensive
# - Hard to scale
# - Threading/async complications
Option 2: PyPuppeteer (Node.js in Python)
import asyncio
from pyppeteer import launch
async def capture_screenshot(url):
browser = await launch()
page = await browser.newPage()
await page.goto(url)
await page.screenshot({'path': 'screenshot.png'})
await browser.close()
asyncio.run(capture_screenshot('https://example.com'))
# Problems:
# - Requires Node.js installed
# - Async-only (adds complexity to sync Django code)
# - Still memory-intensive
# - Dependency management nightmare
Option 3: PageBolt API (Simple HTTP)
import requests
def capture_screenshot(url):
response = requests.post('https://api.pagebolt.dev/take_screenshot',
headers={'Authorization': f'Bearer {PAGEBOLT_KEY}'},
json={'url': url}
)
return response.json()['imageUrl']
# Benefits:
# - One HTTP request
# - No extra dependencies (just requests)
# - Fast (~1-2 seconds)
# - Works in any Django view, task, or background job
# - Scales instantly
Django View Examples
Example 1: Screenshot on Demand
Create a view that returns a screenshot as an image:
# views.py
from django.http import HttpResponse
from django.views import View
import requests
import os
PAGEBOLT_KEY = os.getenv('PAGEBOLT_API_KEY')
class CaptureScreenshotView(View):
def post(self, request):
url = request.POST.get('url')
if not url:
return HttpResponse('URL required', status=400)
# Call PageBolt API
response = requests.post('https://api.pagebolt.dev/take_screenshot',
headers={
'Authorization': f'Bearer {PAGEBOLT_KEY}',
'Content-Type': 'application/json'
},
json={
'url': url,
'width': 1280,
'height': 720
}
)
if response.status_code != 200:
return HttpResponse('Screenshot failed', status=500)
image_url = response.json()['imageUrl']
# Download the image and return it
image_response = requests.get(image_url)
return HttpResponse(image_response.content, content_type='image/png')
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('api/screenshot/', views.CaptureScreenshotView.as_view(), name='screenshot'),
]
Usage:
curl -X POST http://localhost:8000/api/screenshot/ \
-d "url=https://example.com" \
-o screenshot.png
Example 2: Generate PDF from Template
Capture a rendered Django template as a PDF:
# views.py
from django.http import HttpResponse
from django.template.loader import render_to_string
import requests
import os
PAGEBOLT_KEY = os.getenv('PAGEBOLT_API_KEY')
class GenerateInvoicePDFView(View):
def get(self, request, invoice_id):
from .models import Invoice
invoice = Invoice.objects.get(id=invoice_id)
# Render Django template to HTML
html_content = render_to_string('invoices/template.html', {
'invoice': invoice,
'request': request,
})
# Send HTML to PageBolt
response = requests.post('https://api.pagebolt.dev/generate_pdf',
headers={
'Authorization': f'Bearer {PAGEBOLT_KEY}',
'Content-Type': 'application/json'
},
json={
'html': html_content,
'format': 'A4',
'margin': '10mm'
}
)
if response.status_code != 200:
return HttpResponse('PDF generation failed', status=500)
pdf_url = response.json()['pdfUrl']
# Download and return PDF
pdf_response = requests.get(pdf_url)
return HttpResponse(
pdf_response.content,
content_type='application/pdf',
headers={'Content-Disposition': 'attachment; filename="invoice.pdf"'}
)
Example 3: Async Celery Task
Capture screenshots asynchronously for long-running jobs:
# tasks.py
from celery import shared_task
import requests
import os
PAGEBOLT_KEY = os.getenv('PAGEBOLT_API_KEY')
@shared_task
def capture_page_screenshot(url, callback_url=None):
"""Capture a screenshot and optionally POST results to a callback."""
response = requests.post('https://api.pagebolt.dev/take_screenshot',
headers={
'Authorization': f'Bearer {PAGEBOLT_KEY}',
'Content-Type': 'application/json'
},
json={'url': url, 'width': 1280, 'height': 720}
)
if response.status_code != 200:
return {'error': 'Screenshot failed'}
image_url = response.json()['imageUrl']
# Store result
from .models import CapturedScreenshot
screenshot = CapturedScreenshot.objects.create(
original_url=url,
image_url=image_url
)
# Notify callback if provided
if callback_url:
requests.post(callback_url, json={'screenshot_id': screenshot.id, 'image_url': image_url})
return {'screenshot_id': screenshot.id, 'image_url': image_url}
# views.py
from django.http import JsonResponse
from .tasks import capture_page_screenshot
class RequestScreenshotView(View):
def post(self, request):
url = request.POST.get('url')
callback_url = request.POST.get('callback_url')
# Queue async task
task = capture_page_screenshot.delay(url, callback_url)
return JsonResponse({
'task_id': task.id,
'message': 'Screenshot capture queued'
})
Creating a Reusable Utility
Create a helper module for consistent API calls:
# pagebolt_utils.py
import requests
import os
from typing import Optional
class PageBoltClient:
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key or os.getenv('PAGEBOLT_API_KEY')
self.base_url = 'https://api.pagebolt.dev'
def _request(self, endpoint: str, payload: dict) -> dict:
"""Make a request to PageBolt API."""
response = requests.post(
f'{self.base_url}/{endpoint}',
headers={
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json'
},
json=payload
)
if response.status_code != 200:
raise Exception(f'PageBolt API error: {response.text}')
return response.json()
def screenshot(self, url: str, **options) -> str:
"""Take a screenshot of a URL. Returns image URL."""
payload = {'url': url, **options}
result = self._request('take_screenshot', payload)
return result['imageUrl']
def screenshot_html(self, html: str, **options) -> str:
"""Take a screenshot of HTML. Returns image URL."""
payload = {'html': html, **options}
result = self._request('take_screenshot', payload)
return result['imageUrl']
def pdf(self, url: str, **options) -> str:
"""Generate a PDF from a URL. Returns PDF URL."""
payload = {'url': url, **options}
result = self._request('generate_pdf', payload)
return result['pdfUrl']
def pdf_html(self, html: str, **options) -> str:
"""Generate a PDF from HTML. Returns PDF URL."""
payload = {'html': html, **options}
result = self._request('generate_pdf', payload)
return result['pdfUrl']
# Usage in views:
from .pagebolt_utils import PageBoltClient
client = PageBoltClient()
image_url = client.screenshot('https://example.com')
pdf_url = client.pdf_html(html_content, format='A4')
Configuration
In your settings.py:
# settings.py
import os
# PageBolt API Key
PAGEBOLT_API_KEY = os.getenv('PAGEBOLT_API_KEY', '')
In your .env:
PAGEBOLT_API_KEY=your_api_key_here
Why PageBolt for Django
| Aspect | Selenium | PyPuppeteer | wkhtmltopdf | PageBolt API |
|---|---|---|---|---|
| Setup | Browser binary + WebDriver | Node.js + npm | System binary | API key |
| Integration | Complex, requires threading | Async-only | Simple but limited | Simple HTTP |
| Scaling | Hard (more servers = more browsers) | Hard (memory) | Medium | Automatic |
| CSS Support | ✅ Full | ✅ Full | ⚠️ Limited | ✅ Full |
| Speed | Slow (~2-5s) | Slow (~2-3s) | Medium (~1s) | Fast (~1-2s) |
| Dependencies | Heavy (browser binary) | Heavy (Node.js) | Medium (system lib) | None (HTTP) |
| Celery-friendly | ⚠️ Tricky | ⚠️ Async only | ✅ Yes | ✅ Yes |
Getting Started
1. Get API key (free tier: 100 requests/month)
# Visit pagebolt.dev, create account, copy key
2. Install requests (probably already installed)
pip install requests
3. Set environment variable
export PAGEBOLT_API_KEY=your_key_here
4. Use in your Django app
# views.py
import requests
import os
PAGEBOLT_KEY = os.getenv('PAGEBOLT_API_KEY')
def my_view(request):
response = requests.post('https://api.pagebolt.dev/take_screenshot',
headers={'Authorization': f'Bearer {PAGEBOLT_KEY}'},
json={'url': 'https://example.com'}
)
return HttpResponse(response.json()['imageUrl'])
Next Steps
- Try PageBolt free — 100 requests/month, no credit card.
- Pick your use case — document previews, PDF generation, screenshots. Start with one.
- Copy the utility class above — drop it into your project and reuse everywhere.
- Scale with confidence — API handles the infrastructure, you handle the features.
Stop fighting Selenium and Chrome binaries. Start capturing screenshots in Django.
PageBolt: Screenshots and PDFs for Django, effortlessly. Get started free →
Top comments (0)