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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

For multiple URLs, Python can either launch and manage browser captures with Playwright or submit the URL list to a hosted screenshot API that documents batch jobs. Playwright gives you direct control over each browser page and screenshot; the API route can move rendering and batch progress management to a service. In either case, your code still needs to track each URL, handle failures, and decide how to name and store results.

Choose between Playwright and a screenshot API

Playwright’s Python documentation describes taking a screenshot of a page or locator. It does not describe a built-in bulk queue: the loop, concurrency policy, retries, and result manifest are your application’s responsibility. A hosted service can provide an explicit batch endpoint; the vendor documentation cited here describes submitting multiple URLs and tracking the batch by polling or server-sent events.

Decision Playwright in Python Hosted screenshot API
Capture control Documented page and element screenshots, full-page capture, clipping, formats, scale, masking, path output, or returned bytes. Playwright screenshot guide and Page API. The vendor lists viewport, format, full-page, selector, wait, injection, locale, and geolocation options. Screenshot API batch documentation.
Bulk handling Call the documented per-page capture within a loop or your own queue. The vendor documents submitting several URLs in one batch request and tracking progress.
Output Save to a local path or receive bytes for further processing. The vendor’s single-capture example returns a screenshot URL; confirm batch output details and storage lifecycle in its current docs.
Operational limits The cited Playwright pages do not establish universal throughput or machine-sizing guidance. The vendor documents plan quotas, which can change and should be checked before production use.

There is no evidence here for a universal speed or cost winner. Prefer Playwright when local browser control and direct access to image bytes matter. Prefer a managed batch service when its documented job submission and progress mechanisms fit your workflow.

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

Capture many URLs with Playwright

Install Playwright and its browser binaries in the Python environment you will use for the job:

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

This synchronous example captures a list sequentially, saves PNG files, and writes a CSV manifest so each output can be tied back to its source URL. It catches per-URL errors and continues; failed captures are recorded rather than silently treated as successful.

from csv import DictWriter
from pathlib import Path
from urllib.parse import urlparse
import re

from playwright.sync_api import sync_playwright

URLS = [
    "https://example.com/",
    "https://www.python.org/",
]
OUT = Path("screenshots")
OUT.mkdir(exist_ok=True)

def safe_name(url: str, index: int) -> str:
    host = urlparse(url).netloc or "page"
    host = re.sub(r"[^A-Za-z0-9.-]+", "_", host)
    return f"{index:04d}_{host}.png"

rows = []
with sync_playwright() as p:
    browser = p.chromium.launch()
    try:
        for index, url in enumerate(URLS, start=1):
            path = OUT / safe_name(url, index)
            try:
                page = browser.new_page(viewport={"width": 1440, "height": 900})
                response = page.goto(url, wait_until="domcontentloaded", timeout=30_000)
                page.screenshot(path=str(path), full_page=True)
                rows.append({
                    "url": url,
                    "status": response.status if response else "no response",
                    "file": str(path),
                    "error": "",
                })
                page.close()
            except Exception as exc:
                rows.append({"url": url, "status": "error", "file": "", "error": str(exc)})
    finally:
        browser.close()

with (OUT / "manifest.csv").open("w", newline="", encoding="utf-8") as f:
    writer = DictWriter(f, fieldnames=["url", "status", "file", "error"])
    writer.writeheader()
    writer.writerows(rows)

The sequence follows Playwright’s documented lifecycle: launch a browser, create a page, navigate with page.goto(), capture with page.screenshot(), and close the browser. The 30-second navigation timeout and domcontentloaded wait above are choices for this example, not guarantees that every site is ready at that point. Validate readiness against the target pages. The screenshot documentation shows path output and full_page=True; the latter captures the full scrollable page rather than just the viewport.

Choose the right capture boundary

  • Use the default viewport screenshot when the visible initial screen is all you need.
  • Use full_page=True for the full scrollable document; long pages can produce large images.
  • Use a locator’s screenshot method when you need one element rather than the entire page.
  • Use a clip rectangle when only a defined region matters.
  • Playwright supports output path or screenshot bytes, image format and quality options, scale, animation control, and masks. Consult the Page screenshot API for parameter behavior and compatible combinations.

Async captures and concurrency

For an asynchronous application, Playwright provides async equivalents. A semaphore can cap the number of active captures; choose a limit for your own machine and target sites rather than assuming one concurrency setting is universally safe.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import asyncio
from pathlib import Path
from playwright.async_api import async_playwright

URLS = ["https://example.com/", "https://www.python.org/"]
OUT = Path("async-shots")
OUT.mkdir(exist_ok=True)

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        semaphore = asyncio.Semaphore(3)  # Tune for your environment.

        async def capture(index, url):
            async with semaphore:
                page = await browser.new_page(viewport={"width": 1440, "height": 900})
                try:
                    await page.goto(url, wait_until="domcontentloaded", timeout=30_000)
                    await page.screenshot(path=str(OUT / f"{index:04d}.png"), full_page=True)
                    return {"url": url, "ok": True}
                except Exception as exc:
                    return {"url": url, "ok": False, "error": str(exc)}
                finally:
                    await page.close()

        try:
            results = await asyncio.gather(
                *(capture(i, url) for i, url in enumerate(URLS, start=1))
            )
            for result in results:
                print(result)
        finally:
            await browser.close()

asyncio.run(main())

Each page is closed even after an error, and the browser is closed after the batch. For a large URL set, avoid creating an unbounded number of simultaneous pages: cap in-flight work, persist results as you go, and decide whether retries should target only transient failures. These are application design decisions, not a Playwright-prescribed bulk framework.

Submit multiple URLs to a hosted batch API

The Screenshot API vendor documents POST /api/v1/screenshot/batch for multiple URLs, with a returned batch ID and progress tracking through polling or server-sent events. Its documentation uses bearer-key authentication. The following illustrates the documented batch workflow; check the vendor’s current request schema and response fields before using it, since they may change.

import os
import requests

BASE_URL = "https://screenshotapi.net/api/v1"
API_KEY = os.environ["SCREENSHOT_API_KEY"]

payload = {
    "urls": ["https://example.com/", "https://www.python.org/"],
    "options": {
        "viewport": {"width": 1440, "height": 900},
        "format": "png",
        "fullPage": True,
    },
}

response = requests.post(
    f"{BASE_URL}/screenshot/batch",
    json=payload,
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=30,
)
response.raise_for_status()
batch = response.json()
print(batch)

Store the returned batch ID, then use the batch-status or event-stream route specified in the vendor’s live documentation to follow completion and retrieve results. The reviewed vendor material describes polling or SSE progress but does not establish a durable output-retention period or a complete batch response schema here. Keep credentials outside source control, for example in an environment variable or secret manager.

Settings that affect output and completion

  • Viewport and device scale: determine the rendered dimensions and pixel density. Set them consistently if captures will be compared.
  • Format and quality: the vendor lists PNG, JPEG, WebP, and PDF; quality applies where supported by the chosen format.
  • Full page or selector: capture the full document or a specified element, depending on the endpoint’s current schema.
  • Wait strategy: the vendor documents networkidle2 as its default. A continuously active page may not satisfy network-idle behavior; a page-specific selector or deliberate delay may be more appropriate.
  • Selector wait and extra delay: useful when a known element indicates content readiness, or when a page reveals content shortly after navigation.
  • CSS and JavaScript injection: the vendor lists these for adjusting a page before capture. Validate the effect on your target pages.
  • Locale, timezone, and geolocation: set these when regional rendering is part of the required output.
  • Timeouts and cache: the vendor documents a 30,000 ms navigation timeout and cache options. Treat that timeout as a documented default, not a fit for every site or job.

Batch APIs reduce the amount of client-side browser orchestration, but they do not remove the need to associate results with submitted URLs, detect incomplete jobs, and decide what to do with errors. The vendor’s claims about endpoint behavior and options are vendor documentation, not independent test results.

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

Use ScreenshotNeo to avoid managing browser setup

If you want a single-request screenshot API rather than launching browsers yourself, ScreenshotNeo accepts a URL and returns a screenshot or PDF. Its API supports bulk capture of up to 100 URLs per call, as well as an asynchronous-job option for workflows that need job handling. This example uses the single-shot endpoint; see the ScreenshotNeo API documentation for parameters and batch usage.

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

For a set of URLs, submit a bulk capture request as documented rather than repeatedly starting local browsers. ScreenshotNeo removes known consent banners, newsletter popups, and chat widgets before capture, and those cleanup steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. It also offers an MCP server with screenshot, page-info, and PDF tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month without a card.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Make a bulk workflow dependable

Track every URL to a result

Maintain a manifest with the submitted URL, output filename or returned asset reference, completion state, timestamp, and error details. Use a stable index or an encoded host-plus-path for filenames; sanitize characters and account for duplicate URLs so one result cannot overwrite another.

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

Bound concurrency and retry deliberately

For local browser work, cap simultaneous pages based on memory, CPU, and the behavior of the target sites. For a hosted service, respect its current quota and batch limits. Retry only failures that appear transient, use a finite attempt count with backoff, and avoid retrying a successful capture merely because later processing failed. Neither the cited Playwright pages nor the vendor documentation provides an independent throughput benchmark.

Control cost and storage

Full-page images and high device scale factors can substantially increase output size; choose them only when the extra detail is useful. For an API, check current pricing, rate limits, storage duration, and terms before building a production workflow around a quota. For local capture, budget for browser installation, compute, storage, and maintenance rather than treating the browser as cost-free.

Troubleshooting common failures

  • Navigation times out: the site may be slow, block automated access, or keep loading resources. Increase the timeout only if appropriate, try a more suitable readiness condition, and record the failed URL rather than dropping it.
  • Screenshot is blank or incomplete: navigation completion may precede client-rendered content. Wait for a meaningful selector or a measured delay and confirm the element is visible before capturing.
  • Full-page capture is unexpectedly huge: long documents can create very tall images. Capture a specific element or viewport, or use PDF when a paginated document is the intended output.
  • Element screenshot fails: the locator may match nothing, be hidden, or not yet exist. Wait for the locator and verify that it identifies the intended element.
  • Batch request is rejected: inspect authentication, JSON field names, URL formatting, and current batch constraints in the API’s docs. Do not assume the illustrated schema remains unchanged.
  • Batch appears stuck: use the documented status polling or SSE mechanism and distinguish queued, running, completed, and failed states. Persist the batch ID so a client restart does not lose the job reference.
  • Files overwrite one another: ensure output names are unique, especially for repeated hosts or duplicate URLs; include a stable input index or identifier.
  • Output differs between runs: dynamic page content, locale, timezone, viewport, animation, and readiness timing can change the result. Fix these inputs where repeatability matters.

Frequently asked questions

Can Playwright take screenshots of several websites in Python?

Yes. Call its page screenshot method for each URL in your own loop or async workflow. Playwright’s cited references document the individual capture operations, not a built-in multi-URL batch queue.

Should I use a browser library or an API?

Use Playwright when direct browser control or local image processing is central. Use a hosted service when its managed rendering and documented batch-progress workflow match your operational needs; verify quotas, output handling, and current terms first.

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

Does full-page capture include content that loads only while scrolling?

Playwright documents full-page capture as covering the full scrollable page. Whether a particular site lazy-loads all of its content before the screenshot is a separate readiness issue; validate the resulting image for that site.

Can I rely on the API vendor’s free quota for a long-running project?

No quota should be assumed permanent. The vendor’s documentation reviewed in 2026 states a free-plan limit of 60 requests per minute and 500 screenshots per month; recheck its live plan documentation before depending on those limits.

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.