BlogScreenshots on your device
How to Take Screenshots with pyscreenshot in Python
Capture your full desktop or a selected region with pyscreenshot, configure backends, handle Wayland issues, and know when Pillow is the better choice.
Quick answer: install Pillow and pyscreenshot, call pyscreenshot.grab(), then save the returned Pillow image. Pass bbox=(left, top, right, bottom) to capture only a rectangle.
python3 -m pip install Pillow pyscreenshot
import pyscreenshot as ImageGrab
# Full desktop
image = ImageGrab.grab()
image.save("screenshot.png")
# A 500x500 region starting at (10, 10)
region = ImageGrab.grab(bbox=(10, 10, 510, 510))
region.save("region.png")
grab() returns an image in Pillow memory. Pillow performs the file writing and format conversion. The pyscreenshot README documents this interface and the backend-specific behavior described below.
1. Install pyscreenshot in an isolated environment
Use a virtual environment so the capture package and its dependencies do not alter system Python:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install Pillow pyscreenshot
The project README lists Python 3.9, 3.10, and 3.11 as supported versions. PyPI currently lists pyscreenshot 3.1, released in 2023; check the package metadata and project documentation when selecting a newer interpreter.
2. Capture the full screen
from pathlib import Path
import pyscreenshot as ImageGrab
output = Path("screenshot.png")
image = ImageGrab.grab()
image.save(output)
print(f"Saved {output} ({image.width}x{image.height})")
The call captures the desktop available to the selected backend. It is not an interactive selection tool, and the mouse pointer is not shown according to the project documentation.
3. Capture a rectangular area with bbox
Specify two corners as (left, top, right, bottom). Coordinates are desktop coordinates, and the rectangle uses its upper-left and lower-right corners.
import pyscreenshot as ImageGrab
left, top, right, bottom = 100, 80, 900, 680
image = ImageGrab.grab(bbox=(left, top, right, bottom))
image.save("window-area.png")
Validate coordinates before capturing so a typo does not produce an empty or unexpected image:
def checked_bbox(left, top, right, bottom):
if right <= left or bottom <= top:
raise ValueError("right must be greater than left and bottom greater than top")
return (left, top, right, bottom)
bbox = checked_bbox(10, 10, 510, 510)
ImageGrab.grab(bbox=bbox).save("checked-region.png")
4. Choose or force a capture backend
pyscreenshot is a wrapper rather than a capture engine. It selects an available backend, which can be a Python library, desktop D-Bus service, or command-line utility. Installing the wrapper alone does not guarantee that a backend is installed or usable.
You can request a backend explicitly when that backend is available on the machine:
import pyscreenshot as ImageGrab
image = ImageGrab.grab(backend="scrot")
image.save("scrot-shot.png")
Other integrations documented by the project include Pillow, MSS, xdg-desktop-portal Screenshot, GNOME Shell Screenshot, scrot, maim, ImageMagick, PyQt5, PySide2, wxPython, Grim, Quartz, and macOS screencapture. These names describe possible integrations, not universal availability.
Subprocess isolation versus speed
The README shows a speed-oriented MSS configuration that disables child-process execution:
import pyscreenshot as ImageGrab
image = ImageGrab.grab(backend="mss", childprocess=False)
image.save("mss-shot.png")
Subprocess mode provides isolation. Turning it off may reduce overhead but can expose your main process to backend problems. Benchmark both settings on the target desktop before changing the default.
5. Wayland, X11, GNOME, KDE, and Sway
Wayland support depends on the compositor and the route used by the backend. The project documents three routes:
- xdg-desktop-portal: uses the desktop portal Screenshot D-Bus service and may display a confirmation dialog.
- GNOME: can use
org.gnome.Shell.Screenshot. - wlroots compositors: Grim can use
wlr-screencopy-unstable-v1; the README specifically describes Grim with Sway, not GNOME or KDE.
When both Wayland and X are present, the project prefers the Wayland route because Xwayland cannot be used for screenshot capture. Desktop effects can be visible: KDE may show a notification, GNOME may flash, and the portal can ask for confirmation.
If a capture fails on Wayland, identify the current session and install the matching desktop backend rather than repeatedly reinstalling pyscreenshot:
echo "$XDG_SESSION_TYPE"
echo "$XDG_CURRENT_DESKTOP"
which grim
which scrot
6. Save PNG, JPEG, and other Pillow formats
Use the filename extension or pass Pillow options explicitly. PNG preserves lossless pixels; JPEG is smaller but lossy.
import pyscreenshot as ImageGrab
image = ImageGrab.grab()
image.save("screen.png")
image.save("screen.jpg", format="JPEG", quality=90, optimize=True)
image.save("screen.webp", format="WEBP", quality=85)
If a format fails, verify that the installed Pillow build supports it and that the image mode is compatible. Convert explicitly when needed:
rgb = image.convert("RGB")
rgb.save("screen.jpg", quality=90)
7. Build a reusable command-line screenshot script
#!/usr/bin/env python3
import argparse
from pathlib import Path
import pyscreenshot as ImageGrab
parser = argparse.ArgumentParser()
parser.add_argument("output", nargs="?", default="screenshot.png")
parser.add_argument("--bbox", nargs=4, type=int, metavar=("LEFT", "TOP", "RIGHT", "BOTTOM"))
parser.add_argument("--backend")
args = parser.parse_args()
kwargs = {}
if args.bbox:
left, top, right, bottom = args.bbox
if right <= left or bottom <= top:
parser.error("RIGHT must exceed LEFT and BOTTOM must exceed TOP")
kwargs["bbox"] = (left, top, right, bottom)
if args.backend:
kwargs["backend"] = args.backend
image = ImageGrab.grab(**kwargs)
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
image.save(args.output)
print(f"Saved {args.output}: {image.width}x{image.height}")
Examples:
python capture.py full.png
python capture.py crop.png --bbox 10 10 510 510
python capture.py sway.png --backend grim
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
ImportError: No module named pyscreenshot |
Package installed into a different interpreter. | Run python -m pip install Pillow pyscreenshot with the same python used to run the script. |
| Backend not found or capture command missing | No usable system backend is installed. | Install a backend supported by your desktop, or force one with backend=... after confirming it exists. |
| Black, blank, or incomplete image | Desktop security policy, wrong session route, or unsupported compositor/backend. | Check XDG_SESSION_TYPE, test the desktop portal or compositor-specific route, and try Pillow ImageGrab. |
| Portal confirmation blocks automation | xdg-desktop-portal requires user approval. | Use a permitted desktop service or a compositor backend suitable for unattended capture. |
| Grim works on Sway but not GNOME/KDE | Grim relies on compatible wlroots protocols. | Use GNOME Shell Screenshot or the desktop portal on GNOME/KDE. |
| Coordinates capture the wrong monitor | Multi-monitor layouts can use negative or non-zero origin coordinates. | Inspect the desktop coordinate layout and pass the actual global coordinates in bbox. |
| JPEG save raises a mode error | Image has an unsupported mode such as RGBA. | Call image.convert("RGB") before saving JPEG. |
| Capture is slow | Backend startup, subprocess isolation, or desktop service latency. | Measure available backends; test MSS with childprocess=False only when the speed trade-off is acceptable. |
| Unexpected notification or flash | Wayland desktop effects are part of the capture route. | Choose another backend if your environment permits it, or account for the effect operationally. |
9. Performance and reliability
The README publishes sample timings from Ubuntu 22.04 X11 with specific versions of Python and dependencies, including pyscreenshot 3.1, Pillow 9.0.1, and MSS 7.0.1. Treat those figures as observations for that environment, not universal benchmarks. The project recommends measuring the settings on your own machine.
- Reuse a long-running process when taking many captures to avoid repeated interpreter startup.
- Measure complete elapsed time, including backend startup and file encoding.
- Keep subprocess isolation when reliability matters more than a small speed gain.
- For unattended jobs, confirm that the desktop session is active and unlocked and that portal prompts cannot appear.
- Write to a temporary file and rename it after a successful save so readers never consume a partial image.
10. pyscreenshot or Pillow ImageGrab?
The project’s current guidance is unusually direct: its README says “TL;DR: Use Pillow.” It describes pyscreenshot as obsolete for most cases because Pillow ImageGrab now works on Linux and macOS as well as Windows. pyscreenshot can still be useful when you need one interface over several backends, a particular Wayland route, optional subprocess isolation, or a backend that behaves better in your environment.
| Choose | When it fits |
|---|---|
| Pillow ImageGrab | You want the simplest direct API and its supported capture path works on your operating system. |
| pyscreenshot | You need backend selection, a documented Wayland route, or subprocess options. |
Compare the actual desktop session, backend availability, pointer behavior, prompts, isolation needs, and measured performance. No source supports a universal speed winner.
11. Or skip the browser setup
pyscreenshot captures the desktop of the machine running Python. For website screenshots, a hosted browser API avoids display servers, desktop permissions, and Wayland configuration. ScreenshotNeo returns PNG, JPEG, WebP, or PDF from one GET request; its API documentation lists the 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)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = require('node:fs');
fs.writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
Cookie banners, newsletter popups, and chat widgets are removed before the shot, with each cleanup step configurable. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. An 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 each month without a card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and try the API with 1,000 screenshots per month at no charge.
12. FAQ
Does pyscreenshot capture the mouse pointer?
No. The project documents interactive capture as unsupported and says the pointer is not visible.
Can I capture only one application window?
pyscreenshot’s documented API captures the screen or a coordinate rectangle. To target a window, determine its screen coordinates with a desktop-specific tool, then pass those coordinates as bbox.
Why does installing pyscreenshot not fix a Linux capture error?
Because pyscreenshot wraps other backends. The required desktop service, command-line tool, or Python integration must also be installed and usable in the current session.
Is pyscreenshot faster than Pillow?
There is no general answer. The README provides environment-specific examples and recommends benchmarking your own backend and subprocess settings.
Will this work in a headless CI runner?
Only when the runner provides a usable virtual or desktop display and a compatible backend. A hosted website screenshot API is usually simpler for browser-page captures in CI.


