DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk7 min

How to Schedule Website Screenshots in Python with APScheduler

A practical APScheduler 3.x and Playwright guide to recurring website screenshots, with interval and cron examples, browser setup, reliability guidance and troubleshooting.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use APScheduler to decide when a capture runs and Playwright to open the page and save its screenshot. The example below targets the APScheduler 3.x API and Playwright’s synchronous Python API; it supports both recurring intervals and calendar schedules. For reliable operation, install Playwright’s browser binaries where the job will run, choose an output and overlap policy, and keep the scheduler process alive.

Install APScheduler and Playwright

APScheduler and Playwright have different jobs: APScheduler triggers your Python callable; Playwright controls a browser and captures the page. This example uses the APScheduler 3.x BackgroundScheduler and add_job interface. Do not mix it with the newer APScheduler task-and-schedule API; choose and pin a major version that matches your application.

  1. Install the packages in the Python environment used by the scheduled process: python -m pip install "APScheduler>=3,<4" playwright.
  2. Install Playwright’s Chromium browser binary: python -m playwright install chromium.
  3. On Linux or in a container, install the browser’s required operating-system dependencies as part of the host or image setup. The browser binary and dependencies must be present in the environment that executes the job.

Playwright provides synchronous and asynchronous APIs and runs browsers headlessly by default. The code below uses the synchronous API, which is straightforward for a small standalone scheduler. If your application is already asynchronous, use the matching async Playwright and scheduler integration rather than blocking its event loop with synchronous browser work. See the Playwright Python guide.

Write a capture function and schedule it

Save this as scheduled_screenshots.py. It creates the output directory, opens a page, navigates to the target, waits for the page’s load event, saves a viewport screenshot, and closes browser resources even if navigation or capture fails.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from datetime import datetime
from pathlib import Path
import logging

from apscheduler.schedulers.blocking import BlockingScheduler
from playwright.sync_api import sync_playwright

URL = "https://example.com"
OUTPUT_DIR = Path("screenshots")

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)


def capture_website(url: str = URL) -> None:
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now().astimezone().strftime("%Y%m%d-%H%M%S-%z")
    output_path = OUTPUT_DIR / f"example-{timestamp}.png"

    logging.info("Starting screenshot: %s", url)
    started = datetime.now().astimezone()
    try:
        with sync_playwright() as playwright:
            browser = playwright.chromium.launch()
            try:
                page = browser.new_page()
                page.goto(url, wait_until="load", timeout=60_000)
                page.screenshot(path=str(output_path), full_page=True)
            finally:
                browser.close()

        elapsed = (datetime.now().astimezone() - started).total_seconds()
        logging.info("Saved %s in %.1f seconds", output_path, elapsed)
    except Exception:
        logging.exception("Screenshot failed for %s", url)
        raise


if __name__ == "__main__":
    scheduler = BlockingScheduler(timezone="UTC")

    # Choose ONE trigger: interval or cron.
    scheduler.add_job(
        capture_website,
        trigger="interval",
        minutes=30,
        id="example-site-screenshot",
        max_instances=1,
        coalesce=True,
        misfire_grace_time=300,
    )

    # For every day at 09:00 UTC instead, replace the interval job above with:
    # scheduler.add_job(
    #     capture_website,
    #     trigger="cron",
    #     hour=9,
    #     minute=0,
    #     id="example-site-screenshot",
    #     max_instances=1,
    #     coalesce=True,
    #     misfire_grace_time=3600,
    # )

    logging.info("Scheduler started; press Ctrl+C to stop")
    scheduler.start()

Run it with python scheduled_screenshots.py. A blocking scheduler keeps this standalone process occupied while it waits for jobs. In a larger application, a background scheduler may fit better, but it only runs while the containing process is alive.

Use interval for elapsed cadence

An interval trigger expresses an elapsed period, such as every 30 minutes. It does not guarantee a capture finishes within 30 minutes or that the output timestamps will be exactly 30 minutes apart: browser startup, network delays and page rendering all take time. APScheduler’s interval behavior is described in its 3.x IntervalTrigger reference.

Use cron for calendar time

A cron trigger expresses selected calendar fields, such as weekdays at 09:00. Set the scheduler timezone deliberately. The example uses UTC; if the intended schedule follows local time, replace it with the applicable IANA timezone (for example, America/New_York) and account for daylight-saving transitions. Cron fields are combined to determine matching times; see the 3.x CronTrigger reference.

Choose what the screenshot captures

Viewport or full page

page.screenshot(path=...) captures the visible viewport unless configured otherwise. Set full_page=True to capture the full scrollable page. Full-page output may be much taller and larger than a viewport image; choose based on what you need to compare or archive.

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

Wait for the content you need

page.goto(..., wait_until="load") waits for the page load event, but it cannot guarantee that every site-specific widget or late-loaded component is ready. If a screenshot must include a known element, wait for it explicitly before capture, for example page.locator("main").wait_for(). Avoid relying on a fixed sleep unless the site provides no better readiness condition; a selector-based wait expresses the actual requirement more clearly.

Capture output and retention

The example writes timestamped PNGs to a local screenshots directory. For a long-running schedule, define retention or move files to suitable storage so the directory does not grow without bound. If each run should replace the previous image, use a stable output filename instead of a timestamp, while considering whether another capture could overwrite a file still being used. Playwright can also return screenshot bytes for in-memory processing; its screenshot guide covers viewport, full-page and buffer capture.

Make recurring captures dependable

Process lifetime and restarts

An in-memory scheduler loses its schedule when the Python process exits or crashes. APScheduler 3.x supports persistent job stores, but persistence does not keep the process running: run the scheduler under a service manager or container supervisor, or use a separate worker/scheduling arrangement suited to your deployment. On startup, give persistent jobs explicit IDs and use replace_existing=True when registering them, so each restart does not add another copy of the same job.

The example uses a blocking scheduler and in-memory defaults, so it is suitable as a starting point for a continuously running process, not as a restart-proof deployment by itself. For durable scheduling, configure a persistent job store according to the APScheduler 3.x user guide, and separately arrange process supervision.

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

Overlapping runs and missed times

Browser work can take longer than expected. In APScheduler 3.x, a job defaults to one concurrent instance; if another run becomes due while that instance is still running, the later run may be treated as a misfire. Decide whether to skip, coalesce or allow concurrent captures. The sample sets max_instances=1 and coalesce=True, favoring one active capture and consolidating missed runs rather than launching a backlog. Tune misfire_grace_time to how late a run may be and still be useful. Log each start, success, duration and exception, as in the sample.

Several sites

For sites with independent schedules, failure handling or retention, register one job per site with a stable, unique ID and a function argument for its URL and output name. If all sites share timing and policy, one dispatcher job can iterate a configured target list. With a dispatcher, one slow or failing site can affect the rest unless each target is isolated with its own exception handling and timing decisions.

Troubleshooting common failures

  • Browser executable missing: install the browser binary with python -m playwright install chromium in the same environment that runs the script. Rebuild deployment images after adding it.
  • Linux launch fails due to missing libraries: install Playwright’s browser system dependencies in the host or container image; installing the Python package alone is not enough.
  • Navigation times out: the site may be slow, unreachable from the host, or waiting for a load condition that never completes. Check network access and the URL, then choose an appropriate timeout and readiness condition. Do not simply treat a timeout as a successful screenshot.
  • Screenshot is blank or missing late content: wait for the relevant selector or site-specific readiness signal before capture. A page load event is not proof that asynchronous content has rendered.
  • Job appears to stop after a restart: in-memory state was lost or the process is no longer running. Configure a persistent store for schedules and use a supervisor to keep the process alive; they solve different problems.
  • Duplicate jobs after restarting: assign stable job IDs and register persistent startup jobs with replace_existing=True.
  • Captures fall behind: a job may still be running when its next trigger arrives. Review capture duration, schedule cadence, max_instances, coalescing and misfire grace rather than assuming the trigger guarantees completion on time.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. For a scheduled Python capture, call its API from your APScheduler job instead of installing and launching a browser. The API supports PNG, JPEG or WebP screenshots and PDFs; see the API documentation.

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

ScreenshotNeo accepts cookie/consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers indicate the page verdict and billing status. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

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

Sign up free for 1,000 screenshots a month, with no card required.

Frequently Asked Questions

Does the APScheduler process have to stay running between captures?

Yes. A persistent job store preserves scheduler data, but the scheduler still needs a running process to execute jobs.

Can Playwright save a screenshot without writing it to disk?

Yes. Playwright can return screenshot bytes for in-memory processing; use its screenshot buffer option when your workflow does not need a file.

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.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

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

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.