How to Fix Multiple Screenshots Not Working in Python
Fix overwritten files, full-page captures, browser elements, and multi-monitor screenshots in Python with Playwright and PyAutoGUI.

How to Fix Multiple Screenshots Not Working in Python
Most “multiple screenshots” problems have one of three causes: every iteration writes to the same filename, the code is capturing browser content when you need desktop monitors, or the chosen library only captures the primary display. Identify which result you expect before changing the code.
- Several files from one page or many pages: use a unique output path for every capture.
- A complete scrolling page or a page element: use Playwright’s page, full-page, or locator screenshot methods.
- Several physical monitors: PyAutoGUI currently captures only the primary monitor; use a monitor-aware method documented for your operating system.
If none of these descriptions fits, collect the library and version, operating system, traceback, expected output, actual files produced, and a minimal reproducible example.
1. Stop screenshots from overwriting one another
Screenshot APIs accept a path. If a loop repeatedly uses shot.png, each call writes to that same path. Generate a different name for every URL, page, element, or timestamp.

Playwright: unique files for multiple URLs
from pathlib import Path
from playwright.sync_api import sync_playwright
urls = [
"https://example.com",
"https://example.org",
"https://www.python.org",
]
output_dir = Path("screenshots")
output_dir.mkdir(exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
for index, url in enumerate(urls, start=1):
page.goto(url, wait_until="networkidle")
output_path = output_dir / f"page-{index:03d}.png"
page.screenshot(path=str(output_path), full_page=True)
print(f"saved {url} -> {output_path.resolve()}")
browser.close()
Relative paths are resolved from the process’s current working directory. Print Path.cwd() or the absolute path when you cannot find the files.
Use a safe filename derived from a URL
from pathlib import Path
from urllib.parse import urlparse
import re
from playwright.sync_api import sync_playwright
def filename_for(url, index):
host = urlparse(url).netloc or "page"
host = re.sub(r"[^A-Za-z0-9.-]+", "_", host)
return f"{index:03d}-{host}.png"
urls = ["https://example.com", "https://example.org"]
output_dir = Path("screenshots")
output_dir.mkdir(exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
for index, url in enumerate(urls, 1):
page.goto(url, wait_until="domcontentloaded")
path = output_dir / filename_for(url, index)
page.screenshot(path=str(path))
browser.close()
Do not use only a timestamp when a loop can run several times in the same clock tick. An index plus a timestamp, UUID, or URL-derived name is safer for parallel jobs.
2. Choose the correct Playwright target
Playwright captures browser content. A Page captures the viewport or the full document; a Locator captures a matched element, waits for actionability, and scrolls it into view. A detached element raises an error. See the official Playwright screenshot guide and Locator API.
Viewport screenshot
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1280, "height": 720})
page.goto("https://example.com", wait_until="networkidle")
page.screenshot(path="viewport.png")
browser.close()
Full scrollable page
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com", wait_until="networkidle")
page.screenshot(path="full-page.png", full_page=True)
browser.close()
full_page=True requests the full scrollable document. It does not mean “capture every physical monitor.” Very long or virtualized pages may still need application-specific scrolling and stitching.
Several elements from one page
from pathlib import Path
from playwright.sync_api import sync_playwright
selectors = ["header", "main", "footer"]
out = Path("elements")
out.mkdir(exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com", wait_until="networkidle")
for index, selector in enumerate(selectors, 1):
locator = page.locator(selector).first
locator.wait_for(state="visible")
locator.screenshot(path=str(out / f"element-{index:02d}.png"))
browser.close()
If a selector matches nothing, matches a hidden element, or the element is replaced by the page, wait for a stable selector and verify the DOM before taking the shot.
Async Playwright
import asyncio
from pathlib import Path
from playwright.async_api import async_playwright
async def main():
out = Path("async-shots")
out.mkdir(exist_ok=True)
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
for index, url in enumerate(["https://example.com", "https://example.org"], 1):
await page.goto(url, wait_until="networkidle")
await page.screenshot(path=str(out / f"page-{index}.png"), full_page=True)
await browser.close()
asyncio.run(main())
3. Distinguish browser pages from physical monitors
A browser Page or Locator is not a display. It captures rendered browser content inside an automation session. If “multiple screenshots” means two monitors, a browser automation loop will not select the second display.

PyAutoGUI’s documentation FAQ states: “No, right now PyAutoGUI only handles the primary monitor.” A loop of pyautogui.screenshot() calls therefore produces repeated views of the primary monitor rather than one image per display.
Capture the primary screen or a region with PyAutoGUI
PyAutoGUI’s screenshot feature requires Pillow. Its documentation also identifies scrot as a Linux prerequisite. Install the Python packages and, on Linux, the platform dependency before debugging your loop. See the Screenshot Functions, Cheat Sheet, and documentation FAQ.
from pathlib import Path
import pyautogui
out = Path("desktop-shots")
out.mkdir(exist_ok=True)
width, height = pyautogui.size()
print(f"primary monitor: {width}x{height}")
for index in range(3):
path = out / f"primary-{index:02d}.png"
image = pyautogui.screenshot()
image.save(path)
print(path.resolve())
# left, top, width, height
region = pyautogui.screenshot(region=(0, 0, min(800, width), min(600, height)))
region.save(out / "region.png")
The region tuple uses (left, top, width, height). Compare the coordinates with pyautogui.size() and test a small rectangle first.
What to do for multiple monitors
- Confirm that you really need physical display pixels rather than a browser page.
- Check whether your operating system and chosen capture library document monitor selection and negative coordinates.
- Account for scaling, display arrangement, permissions, and whether the second display is enabled.
- Run a one-region test on each target display before adding a loop.
Do not claim that PyAutoGUI supports multiple displays just because a loop runs without an exception. The searched PyAutoGUI documentation does not verify a particular alternative library, so select one whose own documentation explicitly supports the monitors and operating system you use.
4. A decision table for the right fix
| Expected result | Use | Important detail |
|---|---|---|
| One image per URL | Playwright page.screenshot() |
Generate a unique path in the loop |
| Entire scrollable web page | Playwright with full_page=True |
Wait for content and lazy-loaded assets |
| One image per element | Playwright locator screenshot | Use a stable, visible selector |
| Desktop rectangle | PyAutoGUI with region |
Install Pillow and platform prerequisites |
| Each physical monitor | Monitor-aware OS/library method | PyAutoGUI is primary-monitor-only |
| Image bytes for processing | Playwright screenshot without path |
Save or process the returned bytes yourself |
5. Troubleshooting checklist
Only one file exists
Cause: every call uses the same path, or the output directory is not the directory you inspected.
Fix: print an absolute path for every call, include an index in the filename, and print Path.cwd() when using relative paths.
The files exist but look identical
Cause: the page was captured before navigation or dynamic content finished, or PyAutoGUI captured the same primary display each time.
Fix: await navigation and a suitable readiness condition, verify the URL and a visible selector, or use a monitor-aware desktop method.
full_page=True does not include everything
Cause: content may be lazy-loaded, virtualized, inside an iframe, or added after the network becomes idle.
Fix: scroll or interact to trigger content, wait for the relevant selector, inspect iframe boundaries, and capture the correct document.
Locator screenshot throws a timeout or detached-element error
Cause: the selector is missing, hidden, unstable, or the page replaced the node.
Fix: use a stable selector, wait for visibility, then resolve the locator immediately before capture.
PyAutoGUI reports a missing module or cannot capture on Linux
Cause: Pillow is not installed, or the Linux screenshot prerequisite documented by PyAutoGUI is missing.
Fix: install Pillow in the active virtual environment and install the required platform package, then run a small-region capture.
The second monitor is never captured
Cause: PyAutoGUI supports only the primary monitor.
Fix: choose a desktop capture approach that documents display selection for your OS. A repeated PyAutoGUI call cannot add that capability.
Browser launch or navigation fails
Cause: Playwright browsers are not installed, the URL is unreachable, TLS or authentication blocks navigation, or the timeout is too short.
Fix: install the browser binaries, test the URL manually, log the exception and final URL, and set a timeout appropriate to the page. Do not hide failures by writing an empty or stale file.
6. Reliability and performance
- Create the browser once and reuse it for a batch; create isolated contexts when cookies or authentication must not leak between jobs.
- Use deterministic viewport, timezone, locale, color scheme, and device scale settings when comparing images.
- Wait for a meaningful application condition instead of adding a large fixed sleep. A selector, navigation state, or network-idle condition is easier to reason about.
- Limit concurrency to what the machine can render. Too many simultaneous pages increase memory use and make timeouts more likely.
- Use atomic output handling for production jobs: write to a temporary path, verify the image, then rename it to the final unique path.
- Keep a manifest containing the URL, selector, timestamp, viewport, output path, and exception. This makes missing and duplicate captures diagnosable.
- Retry transient navigation failures with a bounded retry count, but do not retry selector bugs indefinitely.
7. Or skip the browser setup
If your goal is website images rather than controlling a local desktop, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. The API accepts full-page and element captures, device presets or custom viewports, retina scale, dark mode, custom CSS and JavaScript, waits, headers, cookies, user agents, geolocation, caching, signed links, asynchronous jobs, and bulk capture.
See the ScreenshotNeo API documentation for request options.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
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)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and whether it was billed. Its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
8. Cost and operational notes
Local Playwright and PyAutoGUI runs consume your machine’s CPU, memory, browser processes, storage, and maintenance time. A hosted API adds request cost but removes browser installation and display-environment management. For ScreenshotNeo, only clean shots are billed; the response headers expose the verdict and billing result. Caching with a chosen TTL can reduce repeated work, while bulk capture supports up to 100 URLs per call.
FAQ
Why does my loop finish without an error but produce one screenshot?
Check whether every iteration uses the same output path. Also print the absolute path and current working directory.
Can Playwright capture my second physical monitor?
No. Playwright captures browser pages and elements in its browser session, not physical displays.
Does pyautogui.size() list every monitor?
It reports the primary screen dimensions. PyAutoGUI’s documented screenshot support is limited to the primary monitor.
Should I use a delay or network idle?
Prefer a condition tied to the page you need, such as a visible selector. Network idle alone may not mean that client-rendered or lazy content is ready.
How do I report an unresolved case?
Include the exact library and version, OS, traceback, monitor layout, minimal code, expected output, actual files, and whether the target is a page, element, desktop region, or physical monitor.


