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

Use Playwright’s async Python API to open each URL, save a screenshot, and close each page cleanly. The example below uses a semaphore to limit concurrent pages, records failures per URL, and saves full-page PNGs under stable filenames. Set full_page=False if you only need the visible viewport.

Install Playwright and its browser

Install the Python package, then install Chromium for Playwright. Run these commands in the environment that will run the script:

  1. python -m pip install playwright
  2. python -m playwright install chromium

The script uses Playwright’s async API, which fits applications already using asyncio. Playwright also provides a synchronous API; see its Python documentation for the setup and API options.

Bulk screenshot URLs with async Python

Save this as bulk_screenshot.py. It writes one full-page PNG per URL into a screenshots directory. The four-page limit is an example, not a universal recommendation; adjust it for your machine, target pages, and workload.

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://playwright.dev/python/",
]
OUT = Path("screenshots")
MAX_CONCURRENT_PAGES = 4  # Example limit; tune for your workload.

async def main():
    OUT.mkdir(parents=True, exist_ok=True)
    semaphore = asyncio.Semaphore(MAX_CONCURRENT_PAGES)

    async with async_playwright() as p:
        browser = await p.chromium.launch()
        context = await browser.new_context(
            viewport={"width": 1440, "height": 1000}
        )

        async def capture(index, url):
            async with semaphore:
                page = await context.new_page()
                try:
                    response = await page.goto(
                        url, wait_until="load", timeout=30_000
                    )
                    status = response.status if response else None
                    output_path = OUT / f"{index:04d}.png"
                    await page.screenshot(
                        path=str(output_path), full_page=True
                    )
                    return {
                        "url": url,
                        "status": status,
                        "file": str(output_path),
                    }
                except Exception as exc:
                    return {"url": url, "error": str(exc)}
                finally:
                    await page.close()

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

    for result in results:
        print(result)

if __name__ == "__main__":
    asyncio.run(main())

Run it with python bulk_screenshot.py. Each successful result includes the URL, HTTP status when a response is available, and output path. A failure is returned for that URL rather than stopping the other captures. The filenames use list position, avoiding unsafe filenames derived directly from URLs.

Choose what each screenshot captures

Viewport or full page

full_page=True captures the full scrollable document as one image. Use full_page=False for the current viewport only. The viewport dimensions are set when creating the browser context; pages in the same context share context-level emulation settings. See Playwright’s multiple-pages guidance and screenshot API documentation.

Wait for the right page state

wait_until="load" waits for the page load event, but it does not guarantee that a JavaScript application has finished rendering its final content. If a site renders important content after load, wait for a known selector before taking the screenshot, for example:

await page.goto(url, wait_until="load", timeout=30_000)
await page.locator("main article").wait_for(state="visible", timeout=10_000)
await page.screenshot(path=str(output_path), full_page=True)

Use a selector that exists on the target pages. If the list contains unrelated sites, a single selector may not fit all of them; store an expected selector alongside each URL or use a site-specific readiness check.

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

Save to a file or use image bytes

For a straightforward batch, pass path to save directly to disk. If a later step needs to upload or process the image in memory, omit the path and use the returned bytes:

image_bytes = await page.screenshot(full_page=True)

Playwright documents both file output and returned screenshot bytes in its screenshot guide.

Set concurrency, isolation, and cleanup deliberately

Bound parallel work

The semaphore limits how many capture tasks create pages at the same time. Raising the limit can increase simultaneous work, but it also uses more local resources and sends more concurrent requests to target sites. Start conservatively and tune based on observed memory use, failures, site behavior, and the time your own batch takes. Playwright does not publish a universal best concurrency value or a performance benchmark for this task.

Share a context or isolate sessions

The sample uses one browser context and a fresh page per URL. Pages in a context share its emulation and related context-level settings. If captures must not share browser session state, create separate contexts for the relevant jobs; contexts provide isolated browser sessions. See Playwright’s browser context documentation.

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

Close resources even when captures fail

Each page closes in finally, and the outer finally closes the context and browser. The Playwright manager is also used as an async context manager, so its lifecycle is handled when the block exits. This explicit cleanup is important when running batches repeatedly or as part of a longer-lived process. See the browser lifecycle API.

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

Common problems and fixes

  • Browser executable is missing: Run python -m playwright install chromium in the same environment where the package is installed.
  • Navigation times out: The site may be slow, unreachable, or waiting on resources. Check the URL and connectivity, then choose a suitable timeout or wait condition. Avoid making every timeout unlimited; one stalled page should not block an unattended batch indefinitely.
  • The screenshot misses late content: load is not an application-specific readiness signal. Wait for a meaningful selector or other page state before calling screenshot.
  • One target fails and others continue: The sample catches errors per URL and returns them as result records. Inspect those records and retry only the failures if appropriate.
  • Output files overwrite each other: The sample uses a zero-padded index for each URL. If the script runs repeatedly into the same directory, include a batch identifier in the output directory or filename.
  • Machine resources spike: Lower MAX_CONCURRENT_PAGES. Full-page screenshots of long documents can be larger and more demanding than viewport captures.
  • A target rejects automated navigation: Playwright cannot guarantee access to every site. Respect the site’s access rules and avoid aggressive concurrency against targets you do not control.

Or skip the browser setup

If you would rather make one HTTP request than install and manage a browser, ScreenshotNeo returns a screenshot or PDF from a URL. It accepts and removes cookie/consent banners, newsletter popups, and chat widgets before the capture; those cleanup steps can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.

Install the Python dependency with python -m pip install requests, then use:

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)

See the ScreenshotNeo API documentation for request options. It also has 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 free and get 1,000 screenshots a month with no card.

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

Frequently Asked Questions

Can I capture only the visible part of each page?

Yes. Set full_page=False in the page.screenshot() call.

Can I use this pattern with a very large URL list?

Yes, but for lists too large to hold or schedule comfortably in one process, feed URLs in batches and persist each result as it completes rather than creating every task at once.

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.