Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk9 min

Use Node.js and Playwright to Screenshot Every Page in a Sitemap

A runnable Node.js Playwright workflow to expand sitemap indexes, capture listed pages, and track failures without claiming the sitemap covers the entire site.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

You can use Node.js and Playwright to fetch a sitemap, expand any sitemap indexes it references, and save a screenshot for each listed page URL. The script below records successes and failures separately so one broken page does not hide the rest of the run.

It captures URLs in the sitemap files it processes—not necessarily every page on the site. A sitemap is a submitted list, not proof of complete site coverage, and its entries are not a guarantee that pages are canonical, available, or permitted to crawl.

As an Amazon Associate I earn from qualifying purchases.

What the script does

The workflow starts with a sitemap URL. It fetches and parses the XML, distinguishes a regular sitemap from a sitemap index, follows index entries, removes duplicate locations, and filters page URLs to the original site’s origin by default. It then navigates to each accepted URL in a Playwright page and saves a screenshot using a stable filename. A JSONL log records the result for every URL.

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

The script uses viewport screenshots by default for more consistent image dimensions. Set FULL_PAGE=1 to capture the full scrollable document instead. Playwright’s screenshot API accepts a file path and a fullPage option; see the Page screenshot API and navigation API.

#1 Best Overall
Philips 24 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 241V8LB | 1920×1080 LED Monitor, 4ms GtG, Adaptive Sync, HDMI 1.4, Tilt Adjust, Low Blue Light, 4yr Advance Replacement Warranty
  • CRISP CLARITY: This 23.8″ Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
  • WORK SEAMLESSLY: This sleek monitor is virtually bezel-free on three sides, so the screen looks even bigger for the viewer. This minimalistic design also allows for seamless multi-monitor setups that enhance your workflow and boost productivity
  • A BETTER READING EXPERIENCE: For busy office workers, EasyRead mode provides a more paper-like experience for when viewing lengthy documents

Check access and sitemap coverage first

Use this workflow only when you have permission and a legitimate reason to capture the pages. Inspect the site’s applicable crawl rules and avoid sending more requests than the site can reasonably handle. The Robots Exclusion Protocol is crawler guidance, not access authorization; a robots.txt file does not grant access to protected content. RFC 9309 also specifies how crawler rules are interpreted and addresses unavailable, unreachable, redirected, and cached robots.txt responses. See RFC 9309.

A sitemap may be named in robots.txt. Google documents this discovery route in its sitemap guidance; RFC 9309 says crawlers may interpret records such as Sitemap. This example accepts an explicit sitemap URL rather than treating robots.txt as permission to crawl.

The Sitemap Protocol allows up to 50,000 URLs and 50 MB per sitemap file, and up to 50,000 sitemap entries and 50 MB per sitemap index. These are format limits, not a promise that processing such a set will be quick or small. Larger jobs can take considerable time and storage, so the example uses a configurable, bounded number of workers. See the Sitemap Protocol.

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

Set up the project

  1. Install a supported Node.js release for your environment. The documentation consulted for this guide does not establish a minimum Node.js version; check the current Playwright setup documentation before choosing one.

  2. Create a directory, initialize a project, and install Playwright plus an XML parser:

    mkdir sitemap-shots && cd sitemap-shots
    npm init -y
    npm install playwright fast-xml-parser
    npx playwright install chromium

    Rank #2
    Philips 22 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 221V8LB
    • CRISP CLARITY: This 22 inch class (21.5″ viewable) Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
    • 100HZ FAST REFRESH RATE: 100Hz brings your favorite movies and video games to life. Stream, binge, and play effortlessly
    • SMOOTH ACTION WITH ADAPTIVE-SYNC: Adaptive-Sync technology ensures fluid action sequences and rapid response time. Every frame will be rendered smoothly with crystal clarity and without stutter
    • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
    • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
  3. Save the following code as screenshot-sitemap.mjs. It uses ES modules and Chromium, keeps concurrency low by default, and writes images to screenshots/.

    What’s actually slowing this PC down?

    Pick the symptom - the matching free tool is one click away.

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

Runnable Node.js script

import fs from 'node:fs/promises';
import path from 'node:path';
import crypto from 'node:crypto';
import { chromium } from 'playwright';
import { XMLParser } from 'fast-xml-parser';

const sitemapUrl = process.env.SITEMAP_URL;
if (!sitemapUrl) {
  console.error('Set SITEMAP_URL to a sitemap or sitemap-index URL.');
  process.exit(1);
}

const outputDir = process.env.OUTPUT_DIR ?? 'screenshots';
const concurrency = Math.max(1, Number.parseInt(process.env.CONCURRENCY ?? '2', 10) || 2);
const fullPage = process.env.FULL_PAGE === '1';
const navigationTimeoutMs = Math.max(1000, Number.parseInt(process.env.TIMEOUT_MS ?? '30000', 10) || 30000);
const allowExternal = process.env.ALLOW_EXTERNAL === '1';
const parser = new XMLParser({
  ignoreAttributes: false,
  attributeNamePrefix: '@_',
  removeNSPrefix: true,
  trimValues: true,
});

function asArray(value) {
  if (value == null) return [];
  return Array.isArray(value) ? value : [value];
}

function getLocs(entries) {
  return asArray(entries).map((entry) => {
    const loc = typeof entry === 'string' ? entry : entry?.loc;
    return typeof loc === 'string' ? loc.trim() : '';
  }).filter(Boolean);
}

function safeName(url) {
  const parsed = new URL(url);
  const readable = `${parsed.hostname}${parsed.pathname}`
    .replace(/[^a-zA-Z0-9._-]+/g, '_')
    .replace(/^_+|_+$/g, '')
    .slice(0, 120) || 'page';
  const hash = crypto.createHash('sha256').update(url).digest('hex').slice(0, 12);
  return `${readable}-${hash}.png`;
}

async function fetchXml(url) {
  const response = await fetch(url, { redirect: 'follow' });
  if (!response.ok) throw new Error(`Sitemap fetch returned HTTP ${response.status}: ${url}`);
  return parser.parse(await response.text());
}

async function collectSitemap(sitemap, allowedOrigin, seenSitemaps, pageUrls) {
  const normalized = new URL(sitemap).href;
  if (seenSitemaps.has(normalized)) return;
  seenSitemaps.add(normalized);

  const xml = await fetchXml(normalized);
  if (xml.sitemapindex) {
    const childSitemaps = getLocs(xml.sitemapindex.sitemap);
    for (const child of childSitemaps) {
      await collectSitemap(new URL(child, normalized).href, allowedOrigin, seenSitemaps, pageUrls);
    }
    return;
  }

  if (xml.urlset) {
    for (const loc of getLocs(xml.urlset.url)) {
      let page;
      try {
        page = new URL(loc, normalized);
      } catch {
        continue;
      }
      if (!['http:', 'https:'].includes(page.protocol)) continue;
      if (!allowExternal && page.origin !== allowedOrigin) continue;
      pageUrls.add(page.href);
    }
    return;
  }

  throw new Error(`Unrecognized sitemap XML root in ${normalized}`);
}

async function main() {
  const start = new URL(sitemapUrl);
  const pageUrls = new Set();
  const seenSitemaps = new Set();
  await collectSitemap(start.href, start.origin, seenSitemaps, pageUrls);
  const urls = [...pageUrls].sort();

  await fs.mkdir(outputDir, { recursive: true });
  const logPath = path.join(outputDir, 'results.jsonl');
  await fs.writeFile(logPath, '');
  if (urls.length === 0) {
    console.error('No in-scope page URLs found. Check the sitemap URL and ALLOW_EXTERNAL setting.');
    return;
  }

  const browser = await chromium.launch({ headless: true });
  let next = 0;
  let failures = 0;
  async function worker() {
    const page = await browser.newPage({ viewport: { width: 1365, height: 900 } });
    page.setDefaultNavigationTimeout(navigationTimeoutMs);
    try {
      while (true) {
        const index = next++;
        if (index >= urls.length) break;
        const url = urls[index];
        const file = path.join(outputDir, safeName(url));
        try {
          const response = await page.goto(url, { waitUntil: 'load' });
          if (!response) throw new Error('Navigation returned no main-resource response');
          await page.screenshot({ path: file, fullPage });
          await fs.appendFile(logPath, `${JSON.stringify({ url, status: 'ok', httpStatus: response.status(), file })}n`);
          console.log(`OK ${url} -> ${file}`);
        } catch (error) {
          failures++;
          await fs.appendFile(logPath, `${JSON.stringify({ url, status: 'error', error: String(error) })}n`);
          console.error(`FAIL ${url}: ${String(error)}`);
        }
      }
    } finally {
      await page.close();
    }
  }

  try {
    await Promise.all(Array.from({ length: Math.min(concurrency, urls.length) }, () => worker()));
  } finally {
    await browser.close();
  }
  console.log(`Processed ${urls.length} unique in-scope URLs; ${failures} failed. Results: ${logPath}`);
  if (failures > 0) process.exitCode = 2;
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run it and choose the capture behavior

Pass the sitemap URL through an environment variable:

SITEMAP_URL="https://example.com/sitemap.xml" node screenshot-sitemap.mjs

For a sitemap index, use the index URL; the script follows the referenced sitemap files and gathers their page entries. It tracks visited sitemap URLs to avoid fetching the same child more than once. The output filename combines a readable hostname and path with a hash of the full URL, which prevents collisions when distinct URLs share a path but differ in query string or other components.

Setting Default Effect
SITEMAP_URL Required URL of a sitemap or sitemap index to process.
OUTPUT_DIR screenshots Directory for PNG files and results.jsonl.
CONCURRENCY 2 Maximum number of pages navigating and capturing at the same time. Keep it modest and adjust to the site and job.
FULL_PAGE Off Set to 1 to save the full scrollable page instead of the viewport.
TIMEOUT_MS 30000 Navigation timeout in milliseconds. Increase it for slow pages; this does not make a page load successfully.
ALLOW_EXTERNAL Off Set to 1 to include sitemap page URLs whose origin differs from the sitemap URL’s origin.

Example with options:

SITEMAP_URL="https://example.com/sitemap.xml" CONCURRENCY=1 FULL_PAGE=1 OUTPUT_DIR=full-shots node screenshot-sitemap.mjs

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

Viewport or full-page screenshots?

A viewport capture gives each page a fixed visible-screen frame, which is useful for quick site-wide review and more directly comparable image dimensions. A full-page capture includes the scrollable document, but long pages can generate very tall images and larger files. Pages with lazy-loaded media or dynamic content may not have everything ready merely because navigation reached the load event; inspect captures and add page-specific waits or scrolling if your target site requires them.

Rank #3
Sale
Dell 24 Monitor - SE2426H - 23.8-inch FHD (1920x1080) 144Hz 1ms Display, in-Plane Switching (IPS) Technology, AMD FreeSync™, TÜV 3-Star 2X HDMI, Tilt
  • Clear visuals. Fluid motion: A 144Hz refresh rate and 1ms MPRT deliver smooth, tear‑free motion across work, gaming, and streaming for clearer, more fluid viewing.
  • Eye comfort: TÜV Rheinland 3‑star* certification reduces harmful blue light while preserving stunning color quality without compromise. *TÜV Rheinland 3-star eye comfort certification.
  • Wide viewing angle: Get consistent views across a wide 178° /178° viewing angle.
  • In-Plane Switching (IPS): See excellent color accuracy and consistency across wide viewing angles with In-plane Switching (IPS) technology.
  • Ultra-thin bezels: Maximize your viewing experience with thin bezels.

The sample waits for the browser’s load event, then captures. A page that keeps loading network requests indefinitely can make stricter network-idle waits unreliable, so use a selector wait or a short delay only when you know what condition indicates readiness. Playwright’s screenshot API also supports format, quality, scale, clipping, and style controls, documented in the screenshot options.

Failures, logs, and recovery

Each URL produces one JSON object per line in results.jsonl. Successful rows include the response status and output path; failed rows include the error string. The process continues after individual navigation or screenshot errors and exits with status 2 if any page failed. A sitemap fetch or parse failure stops the run, because the URL list may be incomplete.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability, and repeatable comparisons

Concurrency trades elapsed time against load placed on the site and local resource use. Start with the default of two workers, monitor the destination and machine, and lower it for sensitive or rate-limited sites. Each worker reuses one page for its URLs, while separate workers use separate pages in the same browser process.

For visual comparisons, keep the browser version, operating system, viewport, device scale, settings, and execution conditions stable. Playwright notes that “Browser rendering can vary based on the host OS, version, settings, hardware, power source (battery vs. power adapter), headless mode, and other factors.” See its visual comparisons guidance. This script does not claim pixel-identical results across machines or runs.

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

To avoid silently treating a partial run as complete, retain the JSONL file, inspect failures, and rerun only failed URLs after checking their cause. If repeatability matters, pin the browser and dependencies in your project’s lockfile and run in a consistent environment. Do not infer that every sitemap entry is crawlable just because it appears in XML.

Best Value
Sale
Sceptre New 22-Inch Gaming Monitor, FHD 1080p, Up to 144Hz, HDMI, DisplayPort, Built-in Speakers, Machine Black (E225W-FW144 Series, 2026)
  • 【INTEGRATED SPEAKERS】Whether you're at work or in the midst of an intense gaming session, our built-in speakers provide rich and seamless audio, all while keeping your desk clutter-free.
  • 【EASY ON THE EYES】 Protect your eyes and enhance your comfort with Blue-Light Shift technology. This feature reduces harmful blue light emissions from your screen, helping to alleviate eye strain during long hours of use and promoting healthier viewing habits.
  • 【WIDEN YOUR PERSPECTIVE】Our sleek minimal bezel design ensures undivided attention. The nearly bezel-free display seamlessly connects in a dual monitor arrangement, delivering an unobstructed view that lets you focus on more at once, completely distraction-free.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. A single GET request returns an image or PDF, and its API can handle the browser setup and capture job:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

See the ScreenshotNeo API documentation for request options. ScreenshotNeo removes cookie/consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are not billed. Its MCP server lets AI agents take screenshots, and the free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.

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

Sign up free for ScreenshotNeo.

Frequently Asked Questions

Does a sitemap include every page on a site?

No. It lists URLs supplied in that sitemap; it does not establish complete coverage of all reachable pages.

Can I capture URLs on another domain listed in the sitemap?

The sample excludes them by default. Set ALLOW_EXTERNAL=1 only when cross-origin capture is intentional and authorized.

Why does the script save PNG files when Playwright can save other formats?

PNG is the explicit format chosen by the sample’s filename extension. Playwright’s screenshot API also supports JPEG and other documented options.

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.

Free tools Windows power users keep installed

One-click scans. No signup required.

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
Crashes, No Sound, or Screen Glitches?Free driver scan

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.