# Screenshots in Python: Playwright, pyppeteer or an API

> Website screenshots from Python in 2026: pyppeteer is unmaintained and ships Chromium 117, Playwright is current. Tested code, and an API option.

Published 2026-11-06 · https://shareglint.com/en/guides/screenshots-in-python-playwright-pyppeteer

To take website screenshots from Python today, use **Playwright for Python**: `pip install playwright`, `playwright install chromium`, then `page.screenshot(path="shot.png", full_page=True)`. **pyppeteer**, the Python port of Puppeteer that many tutorials still use, is unmaintained — its own README says so — and when we ran it on 2026-11-06 it downloaded **Chromium 117**, a browser from 2023. If you would rather not run a browser at all, a screenshot API takes the URL over HTTP. This page compares the three with code we ran on that date.

## pyppeteer: works, but frozen in 2023

The [pyppeteer README](https://github.com/pyppeteer/pyppeteer) opens with: "This repo is unmaintained and has been outside of minor changes for a long time. Please consider playwright-python as an alternative." The last release on PyPI, 2.0.0, is from February 2024.

It still runs. In the official Playwright Python image (Python 3.12), `pip install pyppeteer==2.0.0` and a first `launch()` downloaded a 183 MB Chromium build into `~/.local/share/pyppeteer`, and a full-page screenshot worked. `browser.version()` reported **HeadlessChrome/117.0.5938.0**. That is the problem: a browser three years behind misses CSS and JavaScript features that current sites use, renders some pages differently from what visitors see, and gets no security fixes — while it loads pages from the internet.

```python
# pyppeteer — shown for comparison; prefer Playwright for new code
import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(args=["--no-sandbox"])
    page = await browser.newPage()
    await page.setViewport({"width": 1280, "height": 800})
    await page.goto("https://example.com", waitUntil="networkidle2")
    await page.screenshot({"path": "page.png", "fullPage": True})
    await browser.close()

asyncio.run(main())
```

If you maintain code like this, moving to Playwright is mostly renaming: `newPage` → `new_page`, options become keyword arguments, `fullPage` becomes `full_page`.

## Playwright for Python: the current choice

[Playwright for Python](https://playwright.dev/python/docs/screenshots) (1.64.1 when we tested) installs its own matched browser builds and has a synchronous API, which keeps scripts simple:

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1280, "height": 800}, device_scale_factor=2)
    page.goto("https://example.com", wait_until="networkidle")

    page.screenshot(path="viewport.png")                    # the visible area
    page.screenshot(path="full.png", full_page=True)        # the whole page
    page.locator("h1").screenshot(path="heading.png")       # one element
    jpeg = page.screenshot(type="jpeg", quality=80)         # bytes, no file

    browser.close()
```

Setup: `pip install playwright`, then `playwright install --with-deps chromium` downloads Chromium and, on Debian or Ubuntu, the system libraries it needs. In Docker, the official `mcr.microsoft.com/playwright/python` image has both; our test above ran there and took about a second for all four captures of a local page, with the viewport file at 2560×1600 (2×) as set.

For many pages, use the async API and a few pages in parallel:

```python
import asyncio
from playwright.async_api import async_playwright

async def shoot(browser, url, path):
    page = await browser.new_page(viewport={"width": 1280, "height": 800})
    try:
        await page.goto(url, wait_until="networkidle", timeout=30_000)
        await page.screenshot(path=path, full_page=True)
    finally:
        await page.close()

async def main(urls):
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        semaphore = asyncio.Semaphore(4)  # at most four pages at a time
        async def limited(i, url):
            async with semaphore:
                await shoot(browser, url, f"shot-{i}.png")
        await asyncio.gather(*(limited(i, u) for i, u in enumerate(urls)))
        await browser.close()

asyncio.run(main(["https://example.com", "https://example.org"]))
```

Everything that makes Puppeteer screenshots go wrong applies here too, because it is the same Chromium: lazy images that stay blank in full-page captures, fixed headers in the middle, missing emoji fonts in containers. Our measured write-ups for [full-page screenshots](https://shareglint.com/en/guides/puppeteer-full-page-screenshot) and [emoji and fonts](https://shareglint.com/en/guides/puppeteer-emoji-and-fonts) carry over one to one; our [Playwright screenshot guide](https://shareglint.com/en/guides/playwright-screenshot) covers Playwright's extras (masking, `animations="disabled"`).

## Selenium

Selenium also takes screenshots from Python (`driver.save_screenshot()`), but its full-page method, `get_full_page_screenshot_as_file()`, exists only for Firefox; with Chrome you end up resizing the window, stitching, or calling the DevTools protocol yourself. For screenshots specifically, Playwright is less work.

## Or no browser at all: an API

Running Chromium from Python means a 150–200 MB browser, its system libraries, and memory per page — fine on a server you control, awkward in AWS Lambda, Cloud Functions or a small container. A screenshot API moves that elsewhere:

```python
import os, requests

r = requests.post(
    "https://shareglint.com/v1/screenshots?wait=true",
    headers={"Authorization": f"Bearer {os.environ['OG_API_KEY']}"},
    json={"url": "https://example.com", "full_page": True, "lazy_load": True, "format": "png"},
    timeout=60,
)
r.raise_for_status()
print(r.json()["url"])  # the finished screenshot
```

`lazy_load` scrolls through the page first and `block_cookie_banners` accepts consent dialogs. A viewport capture is 1 render of quota, a full page 2, and the same URL with the same options within 24 hours is free ([pricing](https://shareglint.com/en/pricing)). The [Python screenshot API guide](https://shareglint.com/en/screenshot-api/python) has async and webhook versions. An API only reaches public pages; for an intranet or pages behind a login, run Playwright yourself.

## Which to choose

- **New Python code that drives a browser:** Playwright.
- **Existing pyppeteer code:** move it to Playwright; the browser it ships is three years old,, and the changes are mostly mechanical renames of methods and options.
- **Screenshots in serverless functions or many sites at scale, public pages only:** an API.

## FAQ

### Is pyppeteer still maintained?

No. Its README says the repository is unmaintained and recommends playwright-python instead. The last release, 2.0.0, is from February 2024, and on 2026-11-06 it still downloaded Chromium 117 from 2023.

### What is the Python equivalent of Puppeteer?

Playwright for Python, maintained by Microsoft. Its API is close to Puppeteer's (page.goto, page.screenshot) and it has both a synchronous and an asyncio interface. Version 1.64.1 was current when we tested.

### How do I take a full-page screenshot in Python?

With Playwright: page.screenshot(path='full.png', full_page=True) after page.goto(url). For lazy-loaded images, scroll through the page first; the same rules as in Puppeteer apply.

### Can I take screenshots in Python without installing a browser?

Yes, with a screenshot API: one HTTP request with the URL returns the image. That suits serverless functions and small servers where installing Chromium and its system libraries is a burden.
