How to Generate Website Thumbnails for a List of URLs with Playwright
Generate consistent website thumbnails from a URL list with Playwright. Choose viewport or full-page captures, save each result safely, and handle failures.
Use Playwright to automate website thumbnails for a list of URLs: launch a browser once, visit each URL, save a screenshot to a unique file, and close each page when done. A thumbnail usually means a viewport-sized preview; a full-page screenshot is a much taller image of the scrollable document. Choose which output you need before you start. This guide uses Node.js for the main example and includes Python and cURL options.
For the search question “How do I take screenshots of multiple websites with Playwright?”, the short answer is to put Playwright’s normal navigate-and-screenshot steps in a loop. The examples below process URLs sequentially so each failure can be recorded without stopping the rest of the list.
1. Choose the thumbnail output
| Output | Use it when | Playwright setting |
|---|---|---|
| Viewport thumbnail | You need similarly sized previews of the visible top of each site. | Set a viewport and use fullPage: false (the default). |
| Full-page screenshot | You need the whole scrollable document, including content below the fold. | Use fullPage: true in JavaScript or full_page=True in Python. |
| Element or region | You need a particular component or area rather than the entire viewport. | Use a locator screenshot or screenshot clipping options. |
A viewport setting makes the browser’s CSS viewport consistent, but it does not guarantee that all sites render identical layouts: responsive breakpoints, fonts, consent overlays, redirects, and site-specific behavior can change what appears.
2. Install Playwright
For Node.js, create a project and install Playwright. The browser binaries must also be installed:
npm init -y
npm install playwright
npx playwright install chromium
If using import statements in a .js file, configure the project for ES modules (for example, set "type": "module" in package.json) or save the script with an .mjs extension.
3. Capture a list of URLs with Node.js
This sequential script saves one viewport PNG per URL. It reuses one browser, gives every input an indexed filename, reports failures per URL, and closes pages and the browser in cleanup blocks.
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
const urls = [
'https://example.com',
'https://playwright.dev',
];
const outputDir = 'thumbnails';
const viewport = { width: 1280, height: 720 };
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch();
const failures = [];
try {
for (const [index, url] of urls.entries()) {
const page = await browser.newPage({ viewport });
const filename = `${outputDir}/${String(index + 1).padStart(3, '0')}.png`;
try {
await page.goto(url, { waitUntil: 'load', timeout: 30_000 });
await page.screenshot({ path: filename, fullPage: false });
console.log(`Saved ${url} to ${filename}`);
} catch (error) {
failures.push({ url, error: String(error) });
console.error(`Failed ${url}: ${error}`);
} finally {
await page.close();
}
}
} finally {
await browser.close();
}
if (failures.length) {
console.error('Failed URLs:', JSON.stringify(failures, null, 2));
process.exitCode = 1;
}
The URL list and viewport are yours to choose. Indexed filenames avoid collisions even if multiple URLs share a hostname. In a production batch, persist each failed URL and its error so you can retry or inspect it later.
Use full-page, JPEG, WebP, or a buffer
Set fullPage: true for the full scrollable page. To change format, use a matching extension and the relevant options:
await page.screenshot({ path: 'thumbnails/001.jpg', type: 'jpeg', quality: 80 });
await page.screenshot({ path: 'thumbnails/002.webp', type: 'webp', quality: 80 });
Quality applies to JPEG and WebP, not PNG. The screenshot API can also return image bytes when no path is supplied; use the returned buffer if you want to upload or post-process the image instead of saving it directly. Screenshot scale can target CSS pixels or device pixels. Device-pixel output can be larger; choose the scale that matches the downstream display and storage needs.
Capture an element
When the thumbnail should show one component, locate it and capture the element:
const card = page.locator('main');
await card.screenshot({ path: 'thumbnails/main.png' });
Choose a selector that identifies the intended element on each site. A selector that exists on one URL may be absent or ambiguous on another, so handle that as a per-URL failure or define a site-specific selector mapping.
4. Python version
Install Playwright for Python and its Chromium browser:
python -m pip install playwright
playwright install chromium
This synchronous example creates a page for each URL, saves viewport images, and continues after individual navigation or capture errors:
from pathlib import Path
from playwright.sync_api import sync_playwright
urls = [
'https://example.com',
'https://playwright.dev',
]
output_dir = Path('thumbnails')
output_dir.mkdir(parents=True, exist_ok=True)
failures = []
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
try:
for index, url in enumerate(urls, start=1):
page = browser.new_page(viewport={"width": 1280, "height": 720})
filename = output_dir / f'{index:03}.png'
try:
page.goto(url, wait_until='load', timeout=30_000)
page.screenshot(path=str(filename), full_page=False)
print(f'Saved {url} to {filename}')
except Exception as error:
failures.append({"url": url, "error": str(error)})
print(f'Failed {url}: {error}')
finally:
page.close()
finally:
browser.close()
if failures:
print('Failed URLs:', failures)
For a full-page capture, change the screenshot call to page.screenshot(path=str(filename), full_page=True). Playwright also offers an asynchronous Python API through playwright.async_api for applications already using asyncio.
5. Read URLs from a file and make names safely
For a recurring job, keep one URL per line in a text file rather than editing the script. Never use a raw URL as a filesystem path: URLs contain characters that are unsuitable for filenames, and different URLs can map to the same sanitized name. An index-based filename is simple and collision-resistant within one run; keep a separate manifest mapping each index to its URL.
const { readFile } = await import('node:fs/promises');
const urls = (await readFile('urls.txt', 'utf8'))
.split(/\r?\n/)
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'));
Validate inputs before visiting them: require an explicit http: or https: URL, reject malformed entries, and decide how your application should treat redirects and private or internal addresses. Do not feed untrusted URL lists to a browser worker that can reach sensitive internal services.
6. Waiting for pages and dynamic content
The examples use waitUntil: 'load'. A load event does not guarantee that asynchronous content, client-side rendering, animations, or lazy-loaded images are finished. Choose the least broad wait that matches the target site:
domcontentloadedwaits for initial document parsing and can be appropriate when the site’s visible content is already present.loadwaits for the load event, as used in the examples, but does not prove the page is visually complete.networkidlecan help on pages that settle their network requests, but pages with ongoing polling or analytics may never become idle. It is not a universal readiness test.- For known sites, wait for a meaningful selector or a deliberate short delay after navigation when that is what the page requires. Site-specific waits are more reliable than guessing one condition for every URL.
For full-page captures, lazy-loaded content may not appear until it is scrolled into view. If those images matter, scroll the page in controlled increments before capturing, then allow relevant content to render. The exact scrolling strategy depends on the site and can trigger sticky elements or infinite loading, so inspect representative outputs.
7. Screenshot choices and configuration
| Need | Relevant setting or API | Consideration |
|---|---|---|
| Consistent visible preview | Context/page viewport, such as 1280 × 720 | Pick dimensions that match your thumbnail slot and responsive layout. |
| Whole scrollable document | fullPage: true / full_page=True |
Very long pages can produce large images and may include content that keeps loading. |
| One element or region | Locator screenshot or screenshot clipping | Ensure the element exists and is visible for each target. |
| Compressed output | JPEG or WebP with quality | Lossy formats can reduce file size; inspect text and fine edges for artifacts. |
| Pixel density | Screenshot scale: CSS pixels or device pixels | Device-pixel captures preserve more pixel detail and consume more storage. |
| Image for further processing | Screenshot buffer/returned bytes | Send bytes to a storage or image-processing step without an intermediate file. |
Playwright documents PNG, JPEG, and WebP screenshot output. PNG does not use the quality option. The page screenshot API also supports saving to a path or returning bytes, full-page capture, clipping, and scale controls. See the official Page API and official screenshots guide for the current option details.
8. Throughput, reliability, and cost
The sequential loop is easy to reason about and keeps resource use bounded to one active page at a time. For larger lists, bounded concurrency can reduce total wall-clock time, but it also increases browser memory, network load, and the chance that target sites throttle or block requests. Start sequentially, observe resource use and site behavior, then add a small concurrency limit only if needed. No universal batch speed or safe concurrency number applies to every site and machine.
Reuse the browser process for the batch and close pages promptly. Set a navigation timeout, record failures per URL, and rerun only failures when practical. A retry policy should be limited: repeated retries can waste time and load target sites. Keep output files and a manifest together so partial batches remain useful if the process stops.
Playwright itself is software you run, so costs come from your compute, storage, network, and maintenance rather than a per-screenshot API charge. Full-page and device-pixel captures can use more memory and storage than viewport and CSS-pixel captures. If this is an occasional job, local Playwright may be all you need; if you do not want to install browsers or maintain capture workers, consider a hosted screenshot API.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Executable doesn't exist or browser launch fails |
The Playwright browser binary is not installed for the installed package. | Run npx playwright install chromium for Node.js or playwright install chromium for Python; check the install output. |
| Navigation timeout | The site is slow, unreachable, blocked, or still has long-running requests. | Keep the per-URL error, verify the URL from the same machine, and select a suitable readiness condition. Increase the timeout only when the site reasonably needs more time. |
| Screenshot is blank or missing content | The page may render content after the chosen event, require interaction, or show a blocking/consent layer. | Inspect the page state, wait for a site-specific selector when appropriate, and handle overlays only if your workflow permits it. |
| Images are absent in a full-page shot | Images may be lazy-loaded below the initial viewport. | Scroll through the document before capture and wait for the relevant images to load; check whether the page uses an infinite scroll. |
| Some URLs overwrite the same image | Output paths were derived from non-unique hostnames or unsafe URL strings. | Use an index or another unique identifier and save a URL-to-file manifest. |
| Batch stops at the first bad URL | An error escaped the loop or cleanup is not in a finally block. |
Catch errors per URL, close each page in finally, and close the browser after the loop. |
| Target responds differently to automation | A site may block automated traffic or serve different content. | Respect the target’s terms and access controls; do not assume a screenshot script can bypass bot checks. |
10. Or skip the browser setup
If you would rather send each URL to a hosted API than install and operate Playwright browsers, ScreenshotNeo returns a screenshot or PDF from one GET request. See the ScreenshotNeo API documentation for its 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,
)
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(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await (await import('node:fs/promises')).writeFile('shot.webp', bytes);
Use the same request for each URL in your list, changing the URL parameter and output filename. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
11. Frequently asked questions
Can one Playwright browser handle multiple URLs?
Yes. Navigate a page to each URL in sequence, or create a separate page per URL as in the examples. The Playwright Page API notes that one Browser instance can have multiple Page instances.
Does fullPage mean the same thing as a thumbnail?
No. A thumbnail is usually a viewport preview; fullPage captures the full scrollable document and is often much taller.
Can I take screenshots without writing image files?
Yes. Omit the path and use the screenshot bytes returned by Playwright in memory for upload or processing.
Should I use PNG or WebP?
Choose based on your consumer: PNG avoids lossy compression, while JPEG and WebP support a quality setting. Check the resulting visual quality and storage size for your use case.
References
- Microsoft Playwright Page API (accessed 2026-10-03).
- Microsoft Playwright screenshots guide (accessed 2026-10-03).
- Microsoft Playwright CLI screenshots and PDF guide (accessed 2026-10-03).


