--- title: "How to Take Screenshots in Python: Full Screen, Full Page, and Custom Settings" author: "Shobit Singh" published: "2026-11-02T12:44:19.777Z" updated: "2026-11-02T12:44:19.777Z" url: "https://zyvop.com/how-to-take-screenshots-in-python-full-screen-full-page-and-custom-settings-x23fr" canonical: "https://zyvop.com/how-to-take-screenshots-in-python-full-screen-full-page-and-custom-settings-x23fr" tags: ["Playwright","Python","tutorial","automation","screenshot"] --- # How to Take Screenshots in Python: Full Screen, Full Page, and Custom Settings A practical guide with copy-and-paste code for desktop captures, multi-monitor setups, and complete full-page website screenshots. ![Cover image]() Taking one screenshot by hand is easy. Taking hundreds on a schedule, capturing an entire web page including everything below the fold, or grabbing one exact region of a monitor is where Python shines. In this post you will learn several ways to do it, from a three-line script to a reusable command-line tool, and when to pick each one. Every snippet below is complete, so you can paste it into a `.py` file and run it. ## Which tool should you use? Python has no built-in screenshot module, so the right choice depends on _what_ you want to capture. | Library | Captures | Best for | | --- | --- | --- | | Pillow (`ImageGrab`) | Your desktop | The simplest possible screenshot | | PyAutoGUI | Your desktop | Scripts that already control the mouse and keyboard | | `mss` | Your desktop, every monitor | Speed, multiple monitors, repeated captures | | Playwright | Web pages in a headless browser | Full-page screenshots, device emulation, dark mode | | Selenium | Web pages in a browser | Projects that already use Selenium | Rule of thumb: for your own screen use `mss` or Pillow, and for a website use Playwright. ## Setup You need Python 3.9 or newer. Install everything used in this post: ```bash pip install pillow mss pyautogui playwright playwright install chromium ``` The second command downloads the Chromium build that Playwright controls. You only need it for the website sections. ## 1\. The three-line screenshot with Pillow Pillow’s `ImageGrab` module is the quickest way to capture your screen: ```python from PIL import ImageGrab screenshot = ImageGrab.grab() screenshot.save('screenshot.png') print('Saved', screenshot.size) ``` This captures the primary screen and writes it as a PNG. Pillow picks the file format from the extension, so `screenshot.jpg` or `screenshot.webp` works too. ### Capture only a region The `bbox` argument takes `(left, top, right, bottom)` in pixels: ```python from PIL import ImageGrab region = ImageGrab.grab(bbox=(100, 100, 900, 700)) region.save('region.png') ``` PyAutoGUI does the same job, but its region is `(left, top, width, height)` instead. Mixing the two up is a classic source of wrongly cropped images: ```python import pyautogui pyautogui.screenshot('pyautogui_full.png') pyautogui.screenshot('pyautogui_region.png', region=(100, 100, 800, 600)) ``` ## 2\. Multi-monitor and fast captures with mss If you have more than one display, or you need to take many screenshots quickly, `mss` is the better choice. It treats your displays as a numbered list: index 0 is all monitors combined, index 1 is the primary monitor, index 2 is the second one, and so on. ```python import mss import mss.tools with mss.mss() as sct: print(sct.monitors) # inspect the available monitors # Primary monitor shot = sct.grab(sct.monitors[1]) mss.tools.to_png(shot.rgb, shot.size, output='primary.png') # Every monitor stitched into one image shot = sct.grab(sct.monitors[0]) mss.tools.to_png(shot.rgb, shot.size, output='all_monitors.png') # Each monitor in its own file for number, monitor in enumerate(sct.monitors[1:], start=1): shot = sct.grab(monitor) mss.tools.to_png(shot.rgb, shot.size, output=f'monitor_{number}.png') ``` ### Capture a custom region with mss Here a region is a dictionary, which is much harder to mix up than a tuple: ```python import mss import mss.tools region = {'left': 100, 'top': 100, 'width': 800, 'height': 600} with mss.mss() as sct: shot = sct.grab(region) mss.tools.to_png(shot.rgb, shot.size, output='mss_region.png') ``` ## 3\. A reusable function with delay, format, quality, and resizing In real projects you want options: wait a few seconds so you can switch windows, save as JPEG or WebP with a quality setting, shrink the image, and avoid overwriting old files. This helper uses `mss` for the capture and Pillow for the processing. ```python import time from datetime import datetime from pathlib import Path import mss from PIL import Image def take_screenshot( output_dir='screenshots', delay=0, monitor_index=1, region=None, image_format='png', quality=90, scale=1.0, ): '''Capture the screen and return the path of the saved file. delay seconds to wait before capturing monitor_index 0 = all monitors, 1 = primary, 2 = second monitor, ... region optional dict: {'left': 0, 'top': 0, 'width': 800, 'height': 600} image_format 'png', 'jpg' or 'webp' quality 1-100, used for jpg and webp scale 1.0 = original size, 0.5 = half size ''' if delay: time.sleep(delay) folder = Path(output_dir) folder.mkdir(parents=True, exist_ok=True) with mss.mss() as sct: target = region if region else sct.monitors[monitor_index] raw = sct.grab(target) img = Image.frombytes('RGB', raw.size, raw.bgra, 'raw', 'BGRX') if scale != 1.0: new_size = (int(img.width * scale), int(img.height * scale)) img = img.resize(new_size, Image.Resampling.LANCZOS) fmt = image_format.upper() if fmt == 'JPG': fmt = 'JPEG' extension = 'jpg' if fmt == 'JPEG' else fmt.lower() path = folder / f'screenshot_{datetime.now():%Y%m%d_%H%M%S}.{extension}' save_options = {'quality': quality} if fmt in ('JPEG', 'WEBP') else {} img.save(path, format=fmt, **save_options) return path if __name__ == '__main__': # Primary monitor, PNG, timestamped file name print(take_screenshot()) # Wait 5 seconds, then save a half-size JPEG at quality 80 print(take_screenshot(delay=5, image_format='jpg', quality=80, scale=0.5)) # Only the top-left 800x600 area, as WebP print(take_screenshot(region={'left': 0, 'top': 0, 'width': 800, 'height': 600}, image_format='webp')) # All monitors stitched together print(take_screenshot(monitor_index=0)) ``` A few things worth noticing: - The timestamp in the file name means you never overwrite an earlier screenshot. - `Image.frombytes(..., 'BGRX')` converts mss’s raw pixel data into a normal Pillow image, so you can use any Pillow feature on it (cropping, filters, annotations). - JPEG has no transparency, which is why the image is converted to plain RGB first. ## 4\. Take a screenshot every few seconds Creating one `mss` object and reusing it is faster than creating a new one for every shot, which makes it ideal for timelapses or monitoring a long-running job: ```python import time from pathlib import Path import mss import mss.tools def capture_every(seconds, count, folder='timelapse'): Path(folder).mkdir(exist_ok=True) with mss.mss() as sct: for i in range(1, count + 1): shot = sct.grab(sct.monitors[1]) mss.tools.to_png(shot.rgb, shot.size, output=f'{folder}/shot_{i:03d}.png') print(f'Saved shot {i}/{count}') if i < count: time.sleep(seconds) capture_every(seconds=5, count=6) ``` This saves six screenshots, five seconds apart, as `shot_001.png`, `shot_002.png`, and so on. ## 5\. Full-page screenshots of a website with Playwright Desktop tools can only capture what is currently on your screen. If you need a _complete_ screenshot of a web page, including everything you would normally scroll to see, use a headless browser. Playwright makes this a one-line option. ### Viewport vs. full page ```python 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') page.screenshot(path='viewport.png') # only the visible area page.screenshot(path='full_page.png', full_page=True) # the entire scrollable page browser.close() ``` ### Useful settings Most options live on the browser context (how the page is rendered) or on `page.screenshot()` (how the image is saved): ```python from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context( viewport={'width': 1440, 'height': 900}, # window size device_scale_factor=2, # 2x pixels for a sharp, retina-style image color_scheme='dark', # ask the site for its dark theme locale='en-US', ) page = context.new_page() page.goto('https://example.com', wait_until='load') # JPEG with a quality setting page.screenshot(path='page.jpg', type='jpeg', quality=80, full_page=True) # Transparent background (PNG only; only matters if the page has no background color) page.screenshot(path='transparent.png', omit_background=True) # Crop to a specific area of the page page.screenshot(path='clip.png', clip={'x': 0, 'y': 0, 'width': 800, 'height': 400}) # Screenshot of a single element page.locator('h1').screenshot(path='heading.png') browser.close() ``` ### Emulate a phone Set a phone-sized viewport and the mobile flags to see the mobile layout of a site: ```python from playwright.sync_api import sync_playwright IPHONE_UA = ('Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ' 'AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1') with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context( viewport={'width': 390, 'height': 844}, device_scale_factor=3, is_mobile=True, has_touch=True, user_agent=IPHONE_UA, ) page = context.new_page() page.goto('https://example.com') page.screenshot(path='mobile_full.png', full_page=True) browser.close() ``` ### The lazy-loading problem Many modern sites only load images and sections when you scroll to them. A full-page screenshot taken immediately can show blank gaps. The fix is to scroll through the page first, then capture: ```python PAGE_HEIGHT_JS = 'Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)' def scroll_to_bottom(page, step=600, pause_ms=150, max_steps=60): for i in range(max_steps): position = i * step if position >= page.evaluate(PAGE_HEIGHT_JS): break page.evaluate(f'window.scrollTo(0, {position})') page.wait_for_timeout(pause_ms) page.evaluate('window.scrollTo(0, 0)') ``` The `max_steps` limit stops the script from scrolling forever on infinite-scroll pages. ### Putting it all together: a command-line screenshot tool Save this as `webshot.py`. It combines everything above into one tool with sensible defaults: ```python '''webshot.py - capture a full-page screenshot of any URL. Examples: python webshot.py https://example.com python webshot.py https://example.com -o shot.jpg --width 1920 --scale 2 --dark python webshot.py https://example.com --mobile --delay 2 python webshot.py https://example.com --viewport-only ''' import argparse from pathlib import Path from urllib.parse import urlparse from playwright.sync_api import TimeoutError as PlaywrightTimeout from playwright.sync_api import sync_playwright MOBILE_UA = ( 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ' 'AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1' ) PAGE_HEIGHT_JS = 'Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)' def scroll_to_bottom(page, step=600, pause_ms=150, max_steps=60): '''Scroll down in steps so lazy-loaded content has a chance to load.''' for i in range(max_steps): position = i * step if position >= page.evaluate(PAGE_HEIGHT_JS): break page.evaluate(f'window.scrollTo(0, {position})') page.wait_for_timeout(pause_ms) page.evaluate('window.scrollTo(0, 0)') def capture(url, output, width=1366, height=768, scale=1.0, dark=False, mobile=False, full_page=True, delay=0.0, quality=85, timeout_ms=30000): suffix = Path(output).suffix.lower() image_type = 'jpeg' if suffix in ('.jpg', '.jpeg') else 'png' context_options = { 'viewport': {'width': width, 'height': height}, 'device_scale_factor': scale, 'color_scheme': 'dark' if dark else 'light', } if mobile: context_options.update( viewport={'width': 390, 'height': 844}, is_mobile=True, has_touch=True, user_agent=MOBILE_UA, ) with sync_playwright() as p: browser = p.chromium.launch() try: page = browser.new_context(**context_options).new_page() page.goto(url, wait_until='load', timeout=timeout_ms) try: page.wait_for_load_state('networkidle', timeout=5000) except PlaywrightTimeout: pass # some sites never go fully idle, and that is fine if full_page: scroll_to_bottom(page) if delay: page.wait_for_timeout(delay * 1000) options = {'path': str(output), 'full_page': full_page, 'type': image_type} if image_type == 'jpeg': options['quality'] = quality page.screenshot(**options) finally: browser.close() return output def main(): parser = argparse.ArgumentParser(description='Take a screenshot of a web page.') parser.add_argument('url') parser.add_argument('-o', '--output', help='Output file (.png or .jpg). Default: the domain name + .png') parser.add_argument('--width', type=int, default=1366) parser.add_argument('--height', type=int, default=768) parser.add_argument('--scale', type=float, default=1.0, help='Device scale factor (2 = retina)') parser.add_argument('--quality', type=int, default=85, help='JPEG quality, 1-100') parser.add_argument('--delay', type=float, default=0.0, help='Extra seconds to wait before capturing') parser.add_argument('--dark', action='store_true', help='Request the dark color scheme') parser.add_argument('--mobile', action='store_true', help='Emulate a phone') parser.add_argument('--viewport-only', action='store_true', help='Capture only the visible area') args = parser.parse_args() url = args.url if args.url.startswith(('http://', 'https://')) else 'https://' + args.url output = args.output or urlparse(url).netloc.replace(':', '_') + '.png' capture( url, output, width=args.width, height=args.height, scale=args.scale, dark=args.dark, mobile=args.mobile, full_page=not args.viewport_only, delay=args.delay, quality=args.quality, ) print('Saved', output) if __name__ == '__main__': main() ``` Run it from the terminal: ```bash python webshot.py example.com python webshot.py example.com -o retina_dark.jpg --scale 2 --dark python webshot.py example.com --mobile -o phone.png ``` ## Troubleshooting - **The screenshot is black or empty on macOS.** Give your terminal or IDE permission under System Settings, Privacy & Security, Screen Recording, then restart it. - **It fails on Linux with Wayland.** Pillow, PyAutoGUI, and `mss` need an X11 session. Log in with an X11 session, or call a Wayland-friendly tool such as `grim` through `subprocess`. - **Windows crops the image or captures the wrong area.** Display scaling (125% or 150%) can confuse coordinates. Make the process DPI-aware before capturing: ```python import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(2) # Windows only; call before taking screenshots ``` - **Playwright says the browser executable does not exist.** Run `playwright install chromium`. On a bare Linux server or in Docker, use `playwright install --with-deps chromium` to also install system libraries. - **Playwright errors inside Jupyter.** The sync API conflicts with the notebook’s event loop. Run the code as a normal script, or switch to `playwright.async_api`. - **Very tall pages come out cut off or fail.** Browsers limit image height (around 16,000 pixels in Chromium). For extreme pages, capture in sections using the `clip` option. ## Wrapping up Here is the short version: - Use **Pillow** or **PyAutoGUI** for a quick one-off desktop screenshot. - Use **mss** for multiple monitors, speed, and repeated captures. - Use **Playwright** with `full_page=True` for complete website screenshots, and add scrolling, scale, dark mode, or mobile emulation as needed. Once you can capture the screen from code, you can plug it into test reports, monitoring dashboards, documentation generators, or visual regression checks. Pick the snippet that fits your case, tweak the settings, and automate the boring part.