Skip to content

Guide

Take website screenshots in Python

Playwright for Python launches Chromium and writes a PNG. A hosted screenshot API skips that install and returns image bytes from one HTTP call. This guide shows a complete local script first. The DataTuber example is only the route this website implements, using the Python standard library.

Published by
DataTuber
Published
Updated
Reading time
10 min read

Key takeaways

  • Playwright is the direct approach: install the package, install Chromium, open a page, and call page.screenshot.
  • A viewport capture is the window size you set. full_page=True keeps that width and uses the document’s scroll height.
  • The HTTP example uses urllib from the standard library. It does not need requests, an SDK, or a browser on your machine.
  • On this site, POST /api/screenshots accepts a public https URL, a viewport name, and png or jpg. It does not accept a pixel size or a full-page flag.
  • Check the HTTP status before you save the body. A DataTuber error is JSON with a message. A success body is the image.

Ways to capture a page

Two approaches cover most Python screenshot work. Run a browser yourself, or send the URL to a service that runs one for you.

Playwright is the library in the local script. It can open a page on your machine, set a width and height, and capture the full document height. Selenium and other drivers can also do that. This page shows Playwright only.

A hosted screenshot API accepts a page URL and returns an image. Your process does not launch a browser. Authentication, allowed hosts, and viewport fields are defined by that API. Later sections describe only POST /api/screenshots on this DataTuber website. They are not a catalog of other services. The Node.js guide shows the same route from Node.

What to install

Use Python 3. Confirm it with python --version or python3 --version. The HTTP script below uses only modules that ship with Python 3: json, pathlib, and urllib.

  • Playwright script: the playwright package, then Playwright’s Chromium build. pip install does not download the browser.
  • DataTuber script: no third-party package. A verified account and an API key only if you want the image stored for that workspace.
  • Disk and network for the first Chromium download, if you run the local script.

Install Playwright and Chromium

pip install playwright adds the library. playwright install chromium downloads the browser that library launches. Skip the second command and launch fails because the executable is missing. Run both in the same virtual environment.

pip install playwright
playwright install chromium
python screenshot.py

Run these in the project directory, then run the script saved as screenshot.py.

A complete Playwright example

This opens https://example.com at 1280×720, writes viewport.png for that window, writes full-page.png for the full document, then opens the same URL at 390×844 and writes mobile.png. page.close() runs even when navigation raises. browser.close() runs after the three captures. Replace the URL with the page you want.

from playwright.sync_api import sync_playwright


def capture(browser, url, width, height, path, full_page=False):
    page = browser.new_page(viewport={"width": width, "height": height})
    try:
        response = page.goto(url, wait_until="load", timeout=30_000)
        if response is None or not response.ok:
            status = response.status if response else "no response"
            raise RuntimeError(f"Navigation failed ({status}).")
        page.screenshot(path=path, type="png", full_page=full_page)
    finally:
        page.close()


with sync_playwright() as playwright:
    browser = playwright.chromium.launch()
    try:
        capture(browser, "https://example.com", 1280, 720, "viewport.png")
        capture(browser, "https://example.com", 1280, 720, "full-page.png", full_page=True)
        capture(browser, "https://example.com", 390, 844, "mobile.png")
    finally:
        browser.close()

Save as screenshot.py. The script talks to example.com, not to DataTuber. 390×844 matches this site’s mobile preset. Playwright takes those pixels. The API takes the name mobile.

wait_until="load" waits for the load event. Pages that draw after that event can still look unfinished. Wait for a selector you care about before calling screenshot if the first image is blank. networkidle waits until network connections settle, and pages that keep a connection open can sit there until timeout.

Viewport and full page

The viewport is the browser window: a width and a height in CSS pixels. page.screenshot() without full_page captures that rectangle only. Content below the fold is not in the file.

full_page=True keeps the viewport width and extends the image to the document’s scroll height. A short page looks almost the same either way. A long article does not.

Full-page capture is a poor fit for a page that grows forever, such as an infinite scroll. Fixed headers can also repeat or cover content in the stitched image. Capture the viewport, or one element with locator.screenshot(), when the full document is not a stable height.

A mobile window

The third call is another viewport, not a device emulator and not a full-page capture. 390×844 is the mobile size this website’s route uses when you send the name mobile. Change the numbers when you need a different window. The script does not set device_scale_factor, so the PNG is one pixel per CSS pixel.

Browser automation and a hosted API

Choose Playwright when the page is on your machine, behind a login you can script, or when you need an exact width, height, and full-page flag. Choose an HTTP API when you do not want to install or operate a browser. The table states what this website’s route accepts. It does not rank products, and it does not report timings. Screenshot API vs. Playwright expands that choice.

Playwright in your Python process compared with POST /api/screenshots on this website. Not a speed or quality ranking.

What you install

Playwright
playwright, then Chromium
POST /api/screenshots
Nothing beyond Python 3

Who runs the browser

Playwright
Your Python process
POST /api/screenshots
This website’s capture route

Size

Playwright
Any width and height you set
POST /api/screenshots
A viewport name, not pixels

Full page

Playwright
full_page=True
POST /api/screenshots
Not a field on this route

Which URLs

Playwright
Any URL the machine can open, including localhost
POST /api/screenshots
Public https URLs only

Failure you handle

Playwright
A raised navigation or launch error
POST /api/screenshots
An HTTP status and a JSON message

The route this site implements

POST https://datatuber.com/api/screenshots is the screenshot route served by this website. Do not send the call to api.datatuber.com. That host is not this API. The body is JSON with url, viewport, and format.

viewport is desktop, tablet, or mobile. In the current presets, desktop is 1280×720, tablet is 768×1024, mobile is 390×844. format is png or jpg. jpeg is accepted and stored as jpeg. Custom pixel sizes, a full-page flag, and a wait_until field are not part of this request.

The URL has to be https, with no username, password, port, or IP address. The server resolves the hostname and rejects localhost, link-local, and private addresses. http URLs are rejected. Screenshots lists the same three fields. Getting started shows the body in other languages.

Desktop and mobile

The script requests desktop, then mobile, and writes desktop.png and mobile.png. Tablet is the same call with viewport set to tablet. Those names are the whole size contract. Sending 390 and 844 as numbers is not a supported body.

Replace YOUR_API_KEY with a key from a verified account if you want the image stored for that workspace. The dashboard shows the secret once. Leave the placeholder in any example you commit. The homepage demo calls the same path without a key and does not store the image. Create a free account from the sign-in page. Opening the email link is what verifies the address.

import json
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

ORIGIN = "https://datatuber.com"


def capture(url, viewport, image_format, path):
    body = json.dumps(
        {
            "url": url,
            "viewport": viewport,
            "format": image_format,
        }
    ).encode()
    request = Request(
        ORIGIN + "/api/screenshots",
        data=body,
        headers={
            "Authorization": "Bearer YOUR_API_KEY",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    try:
        with urlopen(request) as response:
            content_type = response.headers.get("Content-Type", "")
            if not content_type.startswith("image/"):
                raise RuntimeError(
                    "Expected an image, got " + (content_type or "no content type") + "."
                )
            Path(path).write_bytes(response.read())
    except HTTPError as error:
        raw = error.read().decode("utf-8", errors="replace")
        try:
            payload = json.loads(raw)
        except json.JSONDecodeError:
            payload = None
        message = payload.get("message") if isinstance(payload, dict) else None
        if not isinstance(message, str):
            message = "HTTP " + str(error.code)
        raise RuntimeError(message) from error
    except URLError as error:
        raise RuntimeError("The request did not connect: " + str(error.reason)) from error


capture("https://www.python.org", "desktop", "png", "desktop.png")
capture("https://www.python.org", "mobile", "png", "mobile.png")

Save as datatuber_screenshot.py. Python 3. YOUR_API_KEY is a placeholder. The origin is https://datatuber.com, which is the host serving this site.

Authentication and the response

Send the key as a Bearer token. urllib raises HTTPError for any status other than success, and the handler reads that body as JSON. When the route recognized the failure, the JSON has a string message. A successful response body is the image, with Content-Type image/png or image/jpeg, and cache-control no-store. The script checks that content type before it writes the file.

Writing the body before you know the call succeeded saves an error as if it were a PNG. The sample in Getting started reads the bytes without that check. Use the script on this page when you need the status handled.

Homepage demo compared with a keyed call on the current DataTuber route. Limits come from the beta plan and rate buckets in the account migration.

Authorization

Homepage demo
No Authorization header
API key
Bearer token

Path

Homepage demo
POST /api/screenshots
API key
POST /api/screenshots

Image kept

Homepage demo
Not stored
API key
Stored for that workspace

Allowance

Homepage demo
5 captures an hour for a hashed IP
API key
100 screenshots in a UTC month, and 6 requests a minute

Shared gap

Homepage demo
11 seconds between captures
API key
11 seconds between captures

Nothing is charged before January 1, 2027. Pricing has no checkout.

Common errors

Playwright: if launch fails because the browser executable is missing, run playwright install chromium in that same environment. If the shell cannot find the playwright command, that environment’s scripts directory is not on PATH. If goto raises a timeout, the load event did not happen within timeout. Increase it, or wait for a specific selector instead of the whole page. A connection error means the URL did not open from that machine. A blank PNG usually means the pixels you wanted were not painted yet.

A full-page file that is enormous, or that never finishes, usually means the page keeps growing. Capture the viewport instead.

This site’s route returns JSON, not an image, when the call is refused. The script raises RuntimeError with the message field. "Send a URL, viewport, and format." means a required field was missing or not one of the accepted names. A network refusal is HTTP 403 with error.code URL_SECURITY_BLOCKED and the message "This URL cannot be captured because it does not meet DataTuber's network rules." The message does not name the blocked address. A content-policy refusal uses CONTENT_POLICY_BLOCKED, a category, and "Screenshot capture is not permitted for this URL under DataTuber's Content Safety Policy." URL_UNDER_REVIEW means no image was captured: "This URL needs a review before a screenshot can be returned. No image was captured." Those categories are not detected automatically. See the Content Safety Policy.

"That API key is not valid." means the bearer token did not match a key. "Verify your email before using DataTuber." means the account cannot create or use a key yet. "Screenshot capture is not configured on this server." means this deployment has no capture credentials. A 429 message asks you to wait. "The capture timed out before an image came back." means the capture did not finish in time. Those strings are the messages implemented in this repository.

What this page is based on

Playwright
The script uses Playwright’s Python sync API: chromium.launch, new_page, goto, screenshot, and full_page. No timed run of that script is reported here, and this repository does not pin a Playwright version.
This website
POST /api/screenshots, the URL checks, the viewport presets, the image response, and the account migration in this repository. Checked November 6, 2026.
Not included
No timed live capture, no performance numbers, and no ranking of other screenshot products. The Python samples were checked for syntax and for the request fields. They were not executed against the live route.

Questions

Do I have to install a browser?

For the Playwright script, yes. pip install playwright is not enough. Run playwright install chromium as well. The urllib script does not install a browser on your machine.

Do I need the requests package?

No. The DataTuber example uses urllib.request, which is in the Python 3 standard library. requests can send the same JSON, but this page does not depend on it.

Can I set a pixel size or a full-page flag on the API?

No. POST /api/screenshots accepts the names desktop, tablet, and mobile, and png or jpg. Playwright accepts a width, a height, and full_page=True. That difference is why the local script and the HTTP script are not interchangeable.

Can Playwright screenshot localhost?

Yes, if the page is reachable from the machine running Python. POST /api/screenshots on this site rejects localhost, IP addresses, and private hosts.

Which host do I call for DataTuber?

https://datatuber.com/api/screenshots. api.datatuber.com is not serving this API. Getting started shows the same body in cURL, JavaScript, Python, and PHP.

Will the DataTuber call charge a card?

No payment is collected before January 1, 2027. Pricing has no checkout. Create a free account verifies an email and lets you issue a key.