How to Capture a Webpage Screenshot with Python
Capture viewport, full-page, and element screenshots in Python with Playwright, then learn waits, formats, troubleshooting, and an API alternative.
To capture a webpage screenshot with Python, use Playwright: install the Python package and its browser binaries, open a page, navigate to the URL, save the screenshot, and close the browser.
pip install playwright
playwright install
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="screenshot.png")
browser.close()
This launches Chromium headlessly, captures the current viewport, and writes screenshot.png. Playwright also supports Firefox and WebKit, synchronous and asynchronous Python APIs, full-page captures, element captures, clipping, image formats, quality, scale, and in-memory output.
1. Install Playwright and its browsers
Install both the Python package and the browser binaries:
python -m pip install playwright
python -m playwright install
The browser binaries are tied to the Playwright release. After upgrading Playwright, run the install command again if the required binaries are missing or out of date. Browser requirements and operating-system support can change, so check the official installation guide when setting up a new environment.
2. Capture the current viewport
from playwright.sync_api import sync_playwright
url = "https://example.com"
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto(url)
page.screenshot(path="viewport.png")
browser.close()
page.screenshot() captures what is visible in the page’s current viewport. The default screenshot timeout is 30 seconds. Set a viewport explicitly when consistent dimensions matter across machines.
3. Capture a full scrollable page
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("https://example.com")
page.screenshot(path="full-page.png", full_page=True)
browser.close()
full_page=True captures the full scrollable document instead of only the visible viewport. Very long pages can produce large images and may expose content that is loaded only after scrolling; use a site-appropriate readiness step when necessary.
4. Capture one element
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.locator("header").screenshot(path="header.png")
browser.close()
Use a locator screenshot when you need a component, chart, card, or other specific element. Prefer a stable selector such as a data attribute when you control the page:
page.locator('[data-testid="pricing-card"]').screenshot(path="pricing-card.png")
If the locator matches no element, or matches an element that is not visible, Playwright raises an error. Wait for the element or correct the selector before taking the screenshot.
5. Choose PNG, JPEG, or WebP
page.screenshot(path="page.jpg", type="jpeg", quality=85)
page.screenshot(path="page.webp", type="webp", quality=80)
page.screenshot(path="page.png", type="png")
The image type can be inferred from the path extension. JPEG and WebP support a quality value; PNG is lossless and does not use the quality option. Match the format to the job: PNG for crisp UI text and transparency, JPEG for photographic pages, and WebP when your downstream system supports it.
6. Control scale and clipping
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1200, "height": 800}, device_scale_factor=1)
page.goto("https://example.com")
page.screenshot(
path="region.png",
clip={"x": 100, "y": 80, "width": 800, "height": 500},
scale="css",
)
browser.close()
Use clip for a rectangular region. CSS scale produces one image pixel per CSS pixel; device scale uses device pixels and can create larger images on high-density displays. Set the browser context’s device_scale_factor when you need predictable retina-style output. See the Playwright screenshot API reference for the complete option list.
7. Return screenshot bytes instead of writing a file
from pathlib import Path
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")
image_bytes = page.screenshot(type="png")
Path("screenshot.png").write_bytes(image_bytes)
browser.close()
Omit path to receive bytes. This is useful for uploading directly to object storage, returning an HTTP response, hashing an image, or passing it to an image-processing pipeline without a temporary file.
8. Use the asynchronous Python API
import asyncio
from playwright.async_api import async_playwright
async def capture():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width": 1440, "height": 900})
await page.goto("https://example.com")
await page.screenshot(path="async-screenshot.png", full_page=True)
await browser.close()
asyncio.run(capture())
Use the async API when your application already runs an asyncio event loop or captures several pages concurrently. Keep concurrency bounded so each browser process has enough CPU, memory, and network capacity.
9. Handle dynamic pages deliberately
page.goto() navigates to a URL, but application-specific content may continue rendering afterward. Choose a readiness condition that matches the site:
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/dashboard")
page.locator("[data-testid='dashboard-ready']").wait_for(state="visible")
page.screenshot(path="dashboard.png")
browser.close()
You can also use a short, justified delay for an animation or a known client-side transition:
page.wait_for_timeout(1000)
A selector wait is usually more meaningful than a fixed delay. For lazy-loaded content, make the page reach the state you need before capturing; a full-page screenshot alone does not define an application’s loading policy.
10. Browser and context choices
with sync_playwright() as p:
browser = p.firefox.launch()
context = browser.new_context(
viewport={"width": 1280, "height": 720},
locale="en-US",
timezone_id="America/New_York",
)
page = context.new_page()
page.goto("https://example.com")
page.screenshot(path="firefox.png")
browser.close()
Playwright supports Chromium, Firefox, and WebKit. A browser context lets you set viewport, locale, timezone, user agent, cookies, authentication state, and other per-session settings. Use a fresh context for isolation between jobs.
11. A reusable capture function
from pathlib import Path
from playwright.sync_api import sync_playwright
def capture(url: str, output: str, full_page: bool = False) -> None:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto(url)
page.screenshot(path=output, full_page=full_page)
browser.close()
capture("https://example.com", "example.png", full_page=True)
For a service, keep browser lifetime and context lifetime deliberate. Reusing a browser while creating isolated contexts can reduce startup overhead, while a separate browser per job provides stronger process isolation at higher resource cost.
12. Troubleshooting
| Error or symptom | Cause | Fix |
|---|---|---|
Executable doesn't exist |
The Playwright package is installed but its browser binary is not. | Run python -m playwright install. Rerun it after a Playwright upgrade. |
| Navigation timeout | The server, network, or application did not reach the navigation condition before the timeout. | Check the URL and network access, then choose a suitable navigation timeout and an explicit readiness condition. Do not hide a permanently failing page with an excessive timeout. |
| Screenshot timeout | The page or locator was not ready before the screenshot timeout. | Wait for the required selector, make sure the locator is visible, and inspect whether an overlay or ongoing animation prevents stability. |
| Blank or incomplete image | Client-side rendering, lazy loading, redirects, or an access challenge is still in progress. | Wait for a page-specific ready marker, verify the final URL and response, and provide required authentication or headers. |
| Element not found | The selector is wrong, the element is inside a frame, or the page has not rendered it. | Verify the selector, wait for it, and use the frame locator when the element belongs to an iframe. |
| Unexpected layout | Viewport, device scale, fonts, locale, timezone, or user agent differs between runs. | Set these values explicitly and install any fonts your page requires. |
| Huge full-page file | The document is very long or captured at a high device scale. | Use CSS scale, reduce the viewport width or output format, or capture only the needed element or region. |
| Old browser behavior after an upgrade | Installed binaries do not match the Python package. | Run the browser install command for the active Playwright version and confirm the environment is using the intended Python interpreter. |
13. Performance, reliability, and cost considerations
- Startup: launching a browser is expensive compared with taking another screenshot in an existing browser. In a controlled worker, reuse the browser and create a new context per job.
- Concurrency: limit parallel pages according to CPU, memory, file size, and the target site’s rate limits. More workers can make captures slower when the host is saturated.
- Readiness: a short selector-based wait is generally more reliable than guessing with a long fixed sleep. Define the exact state your screenshot requires.
- Determinism: fix viewport, scale, browser engine, locale, timezone, fonts, and authentication state when comparing images.
- Storage: bytes let you stream directly to storage. JPEG or WebP can reduce transfer size; PNG preserves lossless UI detail.
- Failure handling: record the URL, browser version, final URL, timing, and exception. Retry transient network failures with a limit, while treating authentication failures and bot checks as conditions that need a different response.
- Cost: self-hosted Playwright consumes your own compute, memory, storage, and bandwidth. Browser binaries also need to be installed in each build or runtime image.
14. Or skip the browser setup
If you need screenshots in a script or backend without managing Playwright, ScreenshotNeo provides a single GET request that returns PNG, JPEG, WebP, or PDF. The API accepts the URL and an access key; the complete option set is in the ScreenshotNeo documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
15. FAQ
Can Python take a screenshot without Selenium?
Yes. Playwright is a Python browser-automation library that can launch Chromium, Firefox, or WebKit and capture screenshots.
Why is my screenshot only the visible area?
The default captures the viewport. Pass full_page=True for the full scrollable document.
How do I screenshot a div in Python?
Use page.locator("selector").screenshot(path="element.png") with a selector that identifies the element.
Can I process the screenshot in memory?
Yes. Omit path; Playwright returns image bytes.
Which browser should I use?
Use the engine that matches your rendering requirement. Playwright supports Chromium, Firefox, and WebKit, and the same screenshot concepts apply to each.
Do I need to reinstall browsers every time?
No. Install them in the runtime environment, then rerun the install command when the Playwright version changes or the required binary is absent.


