{"schemaVersion":"1.0","type":"TechArticle","types":["Article","TechArticle"],"slug":"how-to-take-screenshots-in-python-full-screen-full-page-and-custom-settings-x23fr","url":"https://zyvop.com/how-to-take-screenshots-in-python-full-screen-full-page-and-custom-settings-x23fr","title":"How to Take Screenshots in Python: Full Screen, Full Page, and Custom Settings","subtitle":"A practical guide with copy-and-paste code for desktop captures, multi-monitor setups, and complete full-page website screenshots.","tldr":"Learn how to take screenshots in Python using Pillow, PyAutoGUI, mss, and Playwright. Capture your full screen, a custom region, every monitor, or a complete full-page website with working, copy-and-paste code, plus a ready-to-use command-line tool and fixes for common problems.","keywords":["Playwright","Python","tutorial","automation","screenshot"],"entities":["Shobit Singh","Playwright","Python","tutorial","automation","screenshot","ZyVOP"],"keyTakeaways":["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."],"headings":["Which tool should you use?","Setup","1. The three-line screenshot with Pillow","Capture only a region","2. Multi-monitor and fast captures with mss","Capture a custom region with mss","3. A reusable function with delay, format, quality, and resizing","4. Take a screenshot every few seconds","5. Full-page screenshots of a website with Playwright","Viewport vs. full page","Useful settings","Emulate a phone","The lazy-loading problem","Putting it all together: a command-line screenshot tool","Troubleshooting","Wrapping up"],"outboundLinks":[],"contentText":"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: pip install pillow mss pyautogui playwright playwright install chromiumThe 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: 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: 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: 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. 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: 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. 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: 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 &lt; 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 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): 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: 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: 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 &gt;= 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: '''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 &gt;= 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: 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.pngTroubleshooting The screenshot is black or empty on macOS. Give your terminal or IDE permission under System Settings, Privacy &amp; 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: 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.","contentHash":"sha256:a91a502d099d16ecc0c47da5d2acbaa324f1d78754d2930640fdbf84c6768868","authorName":"Shobit Singh","authorUrl":"https://zyvop.com/author/shobit","authorSameAs":[],"category":null,"tags":["Playwright","Python","tutorial","automation","screenshot"],"audience":"Developers, software engineers, and students learning Playwright","tone":"Instructional, practical, code-first","readingTimeMinutes":8,"wordCount":2114,"faqs":null,"primaryTopic":"Playwright","publishedAt":"2026-11-02T12:44:19.777Z","updatedAt":"2026-11-02T12:44:19.777Z","canonicalUrl":"https://zyvop.com/how-to-take-screenshots-in-python-full-screen-full-page-and-custom-settings-x23fr"}