There are three honest ways to screenshot a website in Python: drive Playwright, drive Selenium, or skip the browser and call an API. The code differs, but the hard parts are the same in all three: waiting for the page to be ready and capturing the full scrolling page, not just the viewport. This guide shows each approach with working code and the gotcha that bites people.
Approach 1: Playwright (the best default)
Playwright for Python launches a browser, and a full-page screenshot is one parameter:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto('https://example.com')
page.screenshot(path='example.png', full_page=True)
browser.close()
Install it with pip install playwright then playwright install chromium. The reason to reach for Playwright first: it auto-waits for the page to reach a stable state before the screenshot fires, so you write less timing code, and full_page=True handles the entire scrolling page in one line. It also drives Chromium, Firefox, and WebKit with the same API. See screenshots in Playwright for elements, CI, and the full-page edge cases.
Approach 2: Selenium (when you already use it)
Selenium captures the current window. In Python that is one method:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://example.com')
driver.save_screenshot('example.png')
driver.quit()
The catch: save_screenshot captures only the visible viewport, and Selenium has no native full-page flag. For the whole page you drive the Chrome DevTools Protocol:
import base64
result = driver.execute_cdp_cmd('Page.captureScreenshot', {
'captureBeyondViewport': True,
'fromSurface': True,
})
with open('full.png', 'wb') as f:
f.write(base64.b64decode(result['data']))
This is more code and more browser-specific than Playwright, which is why Selenium is the right pick mainly when you already run a Selenium suite. The full breakdown is in screenshots in Selenium.
The gotcha both share: waiting
The most common bug in either library is capturing before the page is ready. Do not use a fixed time.sleep; wait on something real. In Selenium:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, 'main-content'))
)
Playwright auto-waits for most cases, but for a specific element use page.wait_for_selector('#main-content') before capturing. For web fonts, wait on document.fonts.ready so the image does not show a fallback font.
Approach 3: Skip the browser, call an API
Both libraries above mean you install and operate a browser in Python: drivers, system libraries, memory management, and full-page workarounds. If screenshots are a production feature rather than a step in an existing script, a screenshot API removes all of that. You send one HTTP request:
curl https://api.grabbit.live/v1/grabs \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"width": 1280,
"full_page": true,
"format": "webp"
}'
The same call from Python with requests:
import requests
res = requests.post(
'https://api.grabbit.live/v1/grabs',
headers={'Authorization': 'Bearer sk_live_...'},
json={'url': 'https://example.com', 'width': 1280, 'full_page': True, 'format': 'webp'},
)
image_url = res.json()['image_url']
The response gives you a hosted image URL:
{
"id": "grb_01jx...",
"status": "done",
"image_url": "https://cdn.grabbit.live/grabs/grb_01jx....webp",
"width": 1280,
"format": "webp",
"bytes": 48210,
"execution_ms": 1180
}
The browser patterns map onto request parameters: viewport is width (320 to 1920) and height (240 to 1080), full page is full_page, the element pattern is a selector field, and the explicit wait becomes delay_ms (0 to 10000). format is png, jpeg, or webp.
Which to use
- New code, want the least friction: Playwright. Fast setup, auto-waiting, one-line full page.
- Already running Selenium: keep it, add the CDP call for full page.
- Screenshots are a production feature, not a script: an API, so you do not operate a browser in Python at all.
For the framework deep dives, see screenshots in Playwright and screenshots in Selenium. If you are weighing hosted options, the honest comparison of screenshot APIs covers the trade-offs without the marketing.
Originally published on the Grabbit blog.
Top comments (0)