ScreenshotNeo

BlogHow-to

How to Save a Webpage as an Image Using Python

Use Playwright to render any webpage and save a viewport, full-page, or element screenshot in Python, with options for format, quality, and timing.

By the ScreenshotNeo team1 October 20267 min read

Direct answer: use Playwright for Python to open the page in a real browser, then call page.screenshot(). Use full_page=True for the entire scrollable document, pass path to save directly to disk, or omit it to receive image bytes in memory. Playwright documents PNG, JPEG, and WebP output, locator screenshots for individual elements, animation control, transparency, scale, quality, and screenshot timeouts in its Python screenshot guide and Page API reference.

Quick start: save a full webpage screenshot

Install Playwright and its browser binaries:

python -m pip install playwright
python -m playwright install chromium

Create save_page.py:

from pathlib import Path
from playwright.sync_api import sync_playwright

url = 'https://example.com'
out = Path('example.png')

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={'width': 1440, 'height': 900})
    page.goto(url, wait_until='networkidle')
    page.screenshot(path=str(out), full_page=True)
    browser.close()

print(f'Saved {out}')

Run it with python save_page.py. The ordinary call captures the current viewport; full_page=True captures the full scrollable page. A navigation wait is only a starting point: pages that load data after navigation may need a selector wait or a short delay before capture.

Choose the capture you need

Viewport screenshot

page.screenshot(path='viewport.png')

This saves what is visible in the current viewport. Set the viewport when consistent dimensions matter:

page = browser.new_page(viewport={'width': 1280, 'height': 800})

Full-page screenshot

page.screenshot(path='entire-page.png', full_page=True)

Full-page mode creates a tall image containing the document’s scrollable content. Very long pages can produce large files or exceed downstream image limits, so consider capturing sections or resizing after capture.

One element

card = page.locator('main article').first
card.screenshot(path='article.png')

Locator screenshots are useful for a chart, header, card, or other component. Use a selector that identifies the intended element after the page has rendered.

Save bytes instead of a file

image_bytes = page.screenshot(full_page=True)
with open('page.webp', 'wb') as f:
    f.write(image_bytes)

Without path, Playwright returns bytes. You can send those bytes to object storage, an image processor, or an HTTP response without creating an intermediate file.

Formats, quality, scale, and transparency

The output type is inferred from the filename extension; PNG is the default. Playwright documents PNG, JPEG, and WebP. JPEG and WebP support a quality setting; PNG does not.

page.screenshot(path='page.jpg', type='jpeg', quality=85)
page.screenshot(path='page.webp', type='webp', quality=80)

Use scale='css' for dimensions based on CSS pixels, or scale='device' for higher device-pixel dimensions. The device scale is useful for retina output but increases memory and file size.

page.screenshot(path='retina.png', scale='device')
page.screenshot(path='compact.png', scale='css')

To preserve transparency, omit the default background:

page.screenshot(path='transparent.png', omit_background=True)

Transparency does not apply to JPEG. Choose PNG or WebP when an alpha channel is required.

Wait for the page that you actually want to capture

No single readiness condition works for every site. Choose the condition that matches the page:

page.goto(url, wait_until='domcontentloaded')
page.locator('[data-loaded="true"]').wait_for()
page.screenshot(path='ready.png')

For a known delay, use a short timeout:

page.goto(url)
page.wait_for_timeout(1500)
page.screenshot(path='delayed.png')

Wait for a specific network response when client-side data drives the page:

page.goto(url)
page.wait_for_response(lambda response: '/api/products' in response.url)
page.screenshot(path='products.png')

Animations can make repeated captures differ. Disable them during the screenshot when visual consistency matters:

page.screenshot(path='stable.png', animations='disabled')

The documented screenshot timeout default is 30 seconds. Set a larger value for slow pages or a smaller one for batch jobs:

page.screenshot(path='slow-page.png', timeout=60_000)

Reusable Python functions

Synchronous helper

from pathlib import Path
from playwright.sync_api import sync_playwright

def save_webpage(url: str, output: str, full_page: bool = True) -> None:
    with sync_playwright() as p:
        browser = p.chromium.launch()
        try:
            page = browser.new_page(viewport={'width': 1440, 'height': 900})
            page.goto(url, wait_until='networkidle')
            page.screenshot(path=output, full_page=full_page, animations='disabled')
        finally:
            browser.close()

save_webpage('https://example.com', 'example.png')

Asynchronous helper

import asyncio
from playwright.async_api import async_playwright

async def save_webpage(url: str, output: str) -> None:
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        try:
            page = await browser.new_page(viewport={'width': 1440, 'height': 900})
            await page.goto(url, wait_until='networkidle')
            await page.screenshot(path=output, full_page=True, animations='disabled')
        finally:
            await browser.close()

asyncio.run(save_webpage('https://example.com', 'example.png'))

Useful browser and page settings

Need Setting or technique
Desktop dimensions browser.new_page(viewport={'width': 1440, 'height': 900})
Mobile-like capture Use a smaller viewport; choose a device preset when your project requires one.
Whole document full_page=True
Specific component page.locator(selector).screenshot()
Small output JPEG/WebP with an appropriate quality value and scale='css'
Transparent background omit_background=True with PNG or WebP
Stable animation state animations='disabled'
Slow rendering Increase timeout and wait for the page’s real readiness signal

Common errors and fixes

Error or symptom Likely cause Fix
Executable doesn't exist Browser binaries were not installed. Run python -m playwright install chromium.
Screenshot shows a loading spinner The page’s data arrives after navigation. Wait for a target locator, response, or a measured delay before calling screenshot.
Only the visible area is saved The capture used default viewport mode. Pass full_page=True.
Element not found The selector is wrong or the element has not rendered. Verify the selector and call locator.wait_for() before the element screenshot.
Timeout during capture Slow page, blocked resource, or an element that never appears. Increase the screenshot timeout, use a narrower readiness condition, and inspect the page independently.
Blank or partially styled image Capture happened before fonts, styles, or scripts finished. Wait for the relevant selector or response; avoid assuming that networkidle means visual readiness for every site.
Huge output file Full-page and device-pixel scale multiply image dimensions. Use CSS scale, JPEG/WebP quality, element captures, or post-process the bytes.
Different result on each run Animations, rotating content, ads, or time-dependent data. Disable animations, set a fixed viewport, and control waits and inputs where possible.

Performance, reliability, and cost considerations

  • Launching a browser is expensive compared with reusing one. For batches, keep one browser process open and create or reuse pages carefully.
  • Full-page captures require more memory than viewport or element captures. Limit concurrency when pages are long or media-heavy.
  • Use WebP or JPEG when your consumer does not require lossless PNG or transparency.
  • Set explicit viewport, locale, timezone, and waits when reproducibility matters. Dynamic ads and third-party scripts can still change output.
  • Close pages and browsers in finally blocks so failures do not leak processes.
  • For private or authenticated pages, provide the required browser context credentials or cookies and avoid logging secrets.

Or skip the browser setup

ScreenshotNeo provides a hosted screenshot API. One GET request returns a PNG, JPEG, WebP, or PDF. Cookie and consent banners are accepted and removed before capture, along with 60+ known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server also lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for the complete option list. Basic call:

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 includes full-page and element capture, dark mode, device presets, retina scale, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, resizing, caching, signed links, asynchronous jobs, webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Every feature is on every plan. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.

FAQ

Can Python save a screenshot without writing a temporary file?

Yes. Omit path from page.screenshot(); Playwright returns bytes that you can process or upload directly.

What is the difference between a viewport and a full-page screenshot?

A viewport screenshot contains the currently visible area. A full-page screenshot includes the page’s full scrollable document.

Which format should I choose?

Use PNG for lossless output and transparency, JPEG for smaller photographic images, and WebP when you want modern compression with quality control.

Why does a screenshot differ from what I see in my desktop browser?

Viewport size, device scale, fonts, animations, login state, cookies, geolocation, third-party content, and page timing can all differ. Set the relevant inputs explicitly and wait for a deterministic page state.