Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To capture many pages in Go, start Playwright once, launch one browser, reuse a page, navigate to each URL with an explicit readiness condition, and call Page.Screenshot with a unique path. Set FullPage when each file must contain the entire scrollable document, wait for a site-specific locator when JavaScript renders important content, and close both the browser and Playwright process after the batch.

What you need

  • A Go module and the github.com/playwright-community/playwright-go package.
  • Playwright’s browser binaries installed for the package (follow the package’s installation instructions for your environment).
  • A writable output directory such as screenshots.
  • A URL list and, for dynamic sites, a selector that identifies when the required content is ready.

The example below uses Chromium, sequential navigation, DOM-content-loaded as the baseline, full-page PNG files, and a zero-padded index. It logs an individual failure and continues so one bad URL does not discard successful captures.

Complete Go batch example

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/playwright-community/playwright-go"
)

func main() {
    urls := []string{
        "https://example.com/one",
        "https://example.com/two",
        "https://example.com/three",
    }

    if err := os.MkdirAll("screenshots", 0o755); err != nil {
        log.Fatal(err)
    }

    pw, err := playwright.Run()
    if err != nil {
        log.Fatal(err)
    }
    defer pw.Stop()

    browser, err := pw.Chromium.Launch()
    if err != nil {
        log.Fatal(err)
    }
    defer browser.Close()

    page, err := browser.NewPage()
    if err != nil {
        log.Fatal(err)
    }

    for i, u := range urls {
        if _, err := page.Goto(u, playwright.PageGotoOptions{
            WaitUntil: playwright.WaitUntilStateDomcontentloaded,
        }); err != nil {
            log.Printf("navigation failed for %s: %v", u, err)
            continue
        }

        // Add a site-specific wait here when content is rendered after DOMContentLoaded.
        path := fmt.Sprintf("screenshots/page-%04d.png", i+1)
        if _, err := page.Screenshot(playwright.PageScreenshotOptions{
            Path:     playwright.String(path),
            FullPage: playwright.Bool(true),
        }); err != nil {
            log.Printf("screenshot failed for %s: %v", u, err)
            continue
        }
        log.Printf("saved %s", path)
    }
}

Initialize the module, add the package, install the browser binaries required by your Playwright setup, and run the program with go run .. Page.Screenshot writes to the path supplied in PageScreenshotOptions.Path. Reusing the page keeps the lifecycle straightforward; the deferred calls guarantee cleanup when the function exits.

Make readiness match the site

WaitUntilStateDomcontentloaded means the initial HTML has been parsed. It does not prove that a client-rendered table, chart, image, or consent-controlled section is visible. After navigation, wait for a locator that represents the content you intend to capture.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
if _, err := page.Goto(u, playwright.PageGotoOptions{
    WaitUntil: playwright.WaitUntilStateDomcontentloaded,
}); err != nil {
    log.Printf("navigation failed for %s: %v", u, err)
    continue
}

if _, err := page.Locator("main[data-loaded='true']").WaitFor(); err != nil {
    log.Printf("content did not become ready for %s: %v", u, err)
    continue
}

Use a selector that is stable and meaningful for that site. A fixed delay can help with a known animation, but a readiness locator is usually more deterministic. If a page never reaches the selector, log the URL and decide whether your job should continue or fail fast.

Choose the capture you actually need

Viewport or full document

FullPage: true captures the complete scrollable page as one tall image. Omit it (or set it to false) for the current viewport only. Full-page output is useful for audits and archives; viewport output is better when every image must have identical dimensions.

Whole page or one element

For repeated cards, charts, headers, or other components, capture a locator instead of the document:

card := page.Locator("article.product-card").First()
if _, err := card.Screenshot(playwright.LocatorScreenshotOptions{
    Path: playwright.String("screenshots/card-0001.png"),
}); err != nil {
    log.Printf("element capture failed: %v", err)
}

For several matching elements, iterate their count and assign each a distinct path. Element capture avoids including unrelated page content.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

PNG, JPEG, and WebP

PNG is lossless and a sensible visual-review default. JPEG or WebP can reduce storage when compression is acceptable. Set the screenshot type and, for JPEG, a quality value supported by your installed package version.

CSS scale or device scale

Playwright can render at CSS-pixel scale or device-pixel scale. CSS scale keeps dimensions aligned with the page’s CSS layout; device scale produces higher-density files and therefore larger output. Pick one consistently across a batch.

Other useful screenshot controls

  • Masking: cover dynamic or private regions before saving.
  • Animations: disable or control animation when deterministic output matters.
  • Caret: hide the text caret to prevent frame-to-frame differences.
  • Transparent background: use when the page and format support transparency.
  • Timeout: set a limit appropriate to slow pages rather than allowing a hung capture indefinitely.

Reliable filenames and output organization

Never use the raw URL as a path. Query strings, slashes, Unicode, and duplicate URLs can create invalid names or overwrite earlier files. A zero-padded index is collision-resistant and preserves input order. If operators need to identify a page without opening it, append a sanitized slug while retaining the index, for example page-0007-pricing.png. Keep a manifest containing the index, URL, output path, timestamp, and error text so a later retry can target only failed entries.

Sequential batches, retries, and concurrency

Sequential navigation is the clearest starting point: one page has one active URL, logs are easy to read, and memory demand is predictable. The available documentation does not establish a universal screenshots-per-second rate, memory-per-page figure, or safe concurrency limit, so do not promise a throughput number.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For transient failures, retry a URL a small, bounded number of times and create a fresh page when the existing page is in a bad state. Keep the original URL in every log line. If a failed capture would make the deliverable invalid, return a nonzero process exit after recording all failures; for best-effort archives, continue and report a failure manifest.

Custom concurrency is possible with multiple pages or browser contexts, but it increases CPU, memory, network load, and the chance of triggering site defenses. Start sequentially, measure your own workload, and add bounded workers only when the job requires it.

Common problems and fixes

Playwright or browser launch fails

Cause: the Go package is present but its browser binary or a system dependency is missing. Fix: install the browser binaries and OS dependencies required by your Playwright installation, then rerun the launch step.

Files are empty, partial, or missing client content

Cause: the screenshot ran after DOM parsing but before the application finished rendering. Fix: wait for a stable content locator, and ensure the selector describes the final state rather than a shell element.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Every iteration overwrites one image

Cause: a constant Path. Fix: include the loop index and, if needed, a sanitized slug in every filename.

Full-page capture is unexpectedly huge

Cause: the document is genuinely tall, an expanding element keeps growing, or lazy content is triggered while scrolling. Fix: inspect page layout, hide or stabilize expanding regions, or use a viewport or element capture when a complete document is not required.

Navigation times out

Cause: a slow server, blocked resource, redirect loop, or page that never reaches the selected readiness state. Fix: record the URL and error, verify it manually, choose a readiness condition appropriate to the site, and retry with a bounded policy. Do not silently treat a timeout as a valid screenshot.

Private or changing content makes comparisons noisy

Cause: animations, timestamps, rotating ads, carets, or personalized regions. Fix: disable animations where possible, mask volatile selectors, use consistent context settings, and capture at a controlled viewport and scale.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One request returns a PNG, JPEG, WebP, or PDF, while its capture flow accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.

See the ScreenshotNeo documentation for all options, then call the API with your key:

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

It also supports full-page and selector captures, 12 device presets plus custom viewports, retina scale, PDF paper and page settings, custom CSS and JavaScript, clicks, waits, blocking rules, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Every feature is on every plan: 1,000 shots per month are free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Frequently Asked Questions

Should I create a new page for every URL?

Not by default. Reusing one page keeps the batch lifecycle simple; create a fresh page or context when isolation from prior state is required or a page becomes unhealthy after an error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can Playwright save formats other than PNG?

Yes. The screenshot API supports PNG, JPEG, and WebP; choose the format and quality appropriate for your storage and visual requirements.

How do I capture only a repeated component?

Use a locator’s screenshot method, such as page.Locator("article.product-card").First().Screenshot, and assign a unique path for each matching element.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.