ScreenshotNeo

BlogHow-to

How to Generate Website Thumbnails with Playwright and Python for a Portfolio

Build consistent portfolio thumbnails with Playwright and Python. Capture viewports, full pages, or elements, then choose formats and settings for your layout.

By the ScreenshotNeo team4 October 20268 min read

Use Playwright’s Python API to open each project URL at a deliberate viewport and save a screenshot. For a portfolio card, a viewport screenshot is often a more useful starting point than a full-page capture: it creates a compact preview of the page’s first screen. Use full-page capture when the whole page is the intended subject, and locator capture when you need one component. Playwright supports saving to a file or returning image bytes for further processing. Playwright’s screenshot guide documents these capture choices.

1. Install Playwright and its browser

Start with Python’s synchronous API for a standalone batch script. Install the package and a browser engine:

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

The browser installation is separate from the Python package installation. Run the install command in each environment where the script needs to launch Chromium. Playwright also has an asynchronous API; use it if the surrounding application already uses asyncio. The library getting-started guide describes both APIs.

2. Capture a batch of portfolio pages

This complete synchronous example creates an output directory, reuses one browser, sets a consistent viewport, visits each URL, and saves a viewport screenshot for each project. It waits for the page load event, then allows a short settling interval for late visual changes. Adjust the wait to suit your sites; no single wait condition guarantees that every page is visually settled.

from pathlib import Path
from urllib.parse import urlparse
from playwright.sync_api import sync_playwright

PROJECTS = [
    ("studio-home", "https://example.com/"),
    ("shop-redesign", "https://example.org/"),
]
OUTPUT_DIR = Path("portfolio-thumbnails")
VIEWPORT = {"width": 1440, "height": 1000}


def main():
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    with sync_playwright() as p:
        browser = p.chromium.launch()
        context = browser.new_context(viewport=VIEWPORT, device_scale_factor=1)
        page = context.new_page()

        for name, url in PROJECTS:
            try:
                response = page.goto(url, wait_until="load", timeout=45_000)
                if response is not None and response.status >= 400:
                    print(f"HTTP {response.status}: {url}")
                    continue

                # Give client-side rendering and late layout changes time to settle.
                page.wait_for_timeout(800)
                output_path = OUTPUT_DIR / f"{name}.webp"
                page.screenshot(
                    path=str(output_path),
                    type="webp",
                    quality=82,
                    animations="disabled",
                )
                print(f"Saved {output_path}")
            except Exception as exc:
                print(f"Could not capture {url}: {exc}")

        context.close()
        browser.close()


if __name__ == "__main__":
    main()

Replace the example URLs with your project pages. Keep output names controlled by your own project identifiers rather than deriving file paths directly from arbitrary URLs. The script uses Chromium, but Playwright also supports Firefox and WebKit; browser choice should reflect the rendering target you need.

3. Choose viewport, full-page, or element capture

Viewport screenshots for portfolio cards

page.screenshot(path="thumb.png") captures the current page view. Choose a fixed viewport that matches the thumbnail’s intended crop and layout. A desktop-width capture makes desktop designs comparable; a narrow viewport is appropriate when the portfolio is specifically presenting mobile layouts. Playwright lets you set viewport and device emulation properties in the browser context. See the emulation guide.

Full-page screenshots for long pages

Set full_page=True to capture the full scrollable page:

page.screenshot(path="full-page.png", full_page=True)

This produces a tall image rather than a card-shaped thumbnail. It is useful when a project’s complete page is the subject, or when you will crop or process it afterward. Full-page capture does not turn a long page into a compact, art-directed preview.

Element screenshots for a component or hero

Use a locator when the portfolio preview should show one element, such as a hero section or product card. Playwright scrolls the element into view before taking the screenshot:

hero = page.locator("main .hero").first
hero.screenshot(path="hero.webp", type="webp", quality=82)

Make the selector specific enough to identify the intended element. A locator screenshot of a scrollable container shows only the currently scrolled content within that container. If the target is missing or hidden, the capture will fail rather than produce the intended image.

4. Set stable rendering and image output

Control motion and changing page content

Animations, transitions, rotating banners, clocks, consent dialogs, and personalized content can make repeated captures differ. Screenshot options support disabling CSS animations and adding a stylesheet. For example, hide an editorially irrelevant banner only when that is appropriate for how you represent the project:

page.screenshot(
    path="stable.png",
    style="""
        *, *::before, *::after {
            animation-duration: 0s !important;
            transition-duration: 0s !important;
        }
        .rotating-promo { visibility: hidden !important; }
    """,
    animations="disabled",
)

Prefer stable site data and a deliberate capture state where you control the project. A stylesheet can change what the screenshot shows; keep those changes documented so portfolio previews remain honest representations.

Pick format, quality, and scale

Choice When it can fit Tradeoff
PNG When lossless output or sharp interface details matter Often larger than lossy formats for photographic content
JPEG When a broadly supported compressed image is suitable Lossy compression can soften fine edges
WebP When the destination supports it and compressed output is useful Confirm that your portfolio pipeline and target browsers accept it
CSS scale When one output pixel per CSS pixel matches the destination May look less sharp on high-density displays
Device scale When you want output pixels to follow the device scale factor Produces more pixels and can increase file size

Locator screenshot options document PNG, JPEG, and WebP formats, a quality setting for JPEG and WebP, and scale="css" or scale="device". Choose dimensions and compression based on the card’s rendered size and the detail readers need. Avoid assuming one format or quality level is best for every portfolio. See the locator screenshot API.

5. Return screenshot bytes instead of saving directly

Omit path to receive the image bytes. You can pass these to an image-processing or storage step in your own pipeline:

image_bytes = page.screenshot(type="png")
Path("portfolio-thumbnails/page.png").write_bytes(image_bytes)

This separates capture from file naming or later processing. The screenshot itself still needs to fit your memory and downstream handling limits if you batch many large pages.

6. Make batch runs more reliable

  • Use one browser process for a modest batch and close each context and browser even if an error occurs. For larger jobs, consider bounded concurrency rather than opening an unbounded number of pages.
  • Give each project a stable output name and log the URL, HTTP status when available, and exception for failures.
  • Choose a navigation condition deliberately. load waits for the load event; some pages continue fetching or rendering afterward. A selector wait can be more meaningful when a known page element marks readiness.
  • Set explicit navigation timeouts and handle individual failures so one inaccessible site does not prevent later projects from being captured.
  • Expect differences from fonts, remote assets, animations, personalized responses, geolocation, and browser rendering. Repeated settings improve consistency but do not guarantee identical pixels on every site.
  • For sites you control, provide a stable preview route or capture state. Avoid bypassing access controls or treating a bot challenge as the site’s ordinary appearance.

For many pages, tune the viewport and image options on a small representative set before running the full collection. Full-page images and device-scale output can consume more storage and processing than compact viewport images, so select them only when the additional detail is useful.

7. Troubleshooting

Symptom Likely cause Fix
Browser executable is missing The Playwright package is installed but its browser was not installed in this environment Run python -m playwright install chromium in the active environment.
Navigation times out The site is slow, blocked, or keeps work active after navigation Set an intentional timeout, try a different documented wait condition, and wait for a page-specific readiness selector where possible. Record failures instead of silently saving partial captures.
Screenshot is blank or incomplete Client-side rendering or late assets were not ready, or the page failed to load Check the response and page state; wait for a meaningful selector or suitable readiness condition before capture.
Locator screenshot says no element found The selector is wrong, the element has not appeared, or the element is in a different frame Verify the selector against the page, wait for the locator to become visible, and use the appropriate frame locator if needed.
Thumbnail has the wrong crop The viewport or capture mode does not match the intended card design Set the context viewport explicitly; use viewport capture for a compact view or full-page capture only for a tall result.
Captures vary from run to run Animation, rotating content, changing data, or consent overlays alter the rendered page Stabilize source data where possible; disable animations or inject narrowly scoped screenshot styles.
WebP option is rejected The installed Playwright version or API in use may not support that option Check the installed version and current official documentation; use PNG or JPEG if needed.

8. Cost and performance considerations

Playwright is a library you run in your own environment, so the capture workflow uses your machine or hosted compute, browser installation, and storage. Runtime depends on the sites, assets, wait conditions, image size, and available resources; the research sources provide no benchmark that would support a universal timing estimate. Reusing a browser for a batch avoids repeatedly launching it in the simple workflow above, while bounded concurrency can improve throughput at the cost of additional memory and CPU. Keep timeouts and retries finite, and avoid retrying the same persistent failure indefinitely.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return PNG, JPEG, WebP, or PDF output. The capture can accept cookie and consent banners like a visitor and remove more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots. See the ScreenshotNeo documentation for parameters and options.

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}`);

Replace the target URL with a project page and keep your API key private. Sign up for 1,000 free screenshots a month, with no card required.

10. FAQ

Should every portfolio preview use a full-page screenshot?

No. A full-page image is tall. A fixed viewport is usually easier to fit into a uniform card; choose full-page capture when the full design is what you want readers to inspect.

Can I capture a mobile version of a site?

Yes. Configure a mobile device profile or set a narrow viewport and the relevant device scale settings. Playwright’s emulation options let you target device-like rendering.

Can Playwright capture only one component?

Yes. Take a locator screenshot for a matching element. For a scrollable container, the screenshot reflects its currently scrolled content.

Which image format should I choose?

Choose based on destination support, edge sharpness, image content, and file size. Playwright documents PNG, JPEG, and WebP for locator screenshots; JPEG and WebP accept quality settings.

Sources