ScreenshotNeo

BlogHow-to

How to Make Website Thumbnails for a List of Independent Online Shops

Build a consistent set of shop thumbnails with Playwright: validate URLs, capture the same view, normalize images, handle failures, and review reuse rights.

By the ScreenshotNeo team4 October 20269 min read

To make consistent website thumbnails for a list of independent online shops, capture each shop at the same viewport and browser scale, save one image per canonical URL, resize or crop each image to a shared card ratio, and review the results. Playwright can capture the visible viewport, a full page, or a selected element. The example below uses the viewport because it usually gives directory cards a more comparable first impression.

This guide uses Node.js and Playwright for local browser automation, then shows cURL, Python, and Node.js calls to the ScreenshotNeo website screenshot API. Check each shop’s reuse terms before publishing its screenshot: a capture tool does not grant permission to republish a site’s content.

1. Decide what each thumbnail should show

Choose the capture style before processing the list. Keep the choice consistent unless a particular shop needs a documented exception.

Capture Use it when Tradeoff
Viewport A directory card should show the initial impression of each shop. Content below the fold is omitted.
Full page You need a complete page record for review or documentation. A tall image can be difficult to read when reduced to card size.
Element A stable hero, header, or shop region should be isolated. It depends on a reliable selector. A scrollable element capture shows only the element’s visible content.

Use one canonical URL and stable identifier per shop. Decide how to handle redirects, inaccessible pages, and pages that block automation. There is no universal readiness rule for independently operated shops: a page may be ready after navigation, after a particular element appears, or after a short wait.

2. Set up a repeatable Playwright capture

Install Node.js, create a project, and install Playwright. The first command also downloads its Chromium browser.

npm init -y
npm install playwright
npx playwright install chromium

Create shops.json with a stable filename key and one URL per shop:

[
  { "id": "shop-a", "url": "https://example.com" },
  { "id": "shop-b", "url": "https://example.org" }
]

Save this as capture-shops.mjs. It opens a fresh page for each shop, uses the same viewport, waits for navigation to settle, captures a viewport image, and writes a result record for every URL so one failure does not hide other outcomes.

import { chromium } from 'playwright';
import { readFile, mkdir, writeFile } from 'node:fs/promises';

const shops = JSON.parse(await readFile('shops.json', 'utf8'));
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  viewport: { width: 1280, height: 800 },
  deviceScaleFactor: 1,
  colorScheme: 'light'
});

await mkdir('thumbnails', { recursive: true });
const results = [];

for (const shop of shops) {
  const page = await context.newPage();
  try {
    const response = await page.goto(shop.url, {
      waitUntil: 'domcontentloaded',
      timeout: 45000
    });

    // Optional: wait for a known page landmark if the shop has a stable one.
    // await page.locator('main').waitFor({ state: 'visible', timeout: 10000 });

    // Optional: allow a short time for client-rendered content or fonts.
    // await page.waitForTimeout(1000);

    const filename = `thumbnails/${shop.id}.png`;
    await page.screenshot({ path: filename, type: 'png' });
    results.push({
      id: shop.id,
      url: shop.url,
      finalUrl: page.url(),
      status: response?.status() ?? null,
      file: filename,
      outcome: 'captured'
    });
  } catch (error) {
    results.push({
      id: shop.id,
      url: shop.url,
      outcome: 'failed',
      error: String(error)
    });
  } finally {
    await page.close();
  }
}

await writeFile('thumbnails/results.json', JSON.stringify(results, null, 2));
await browser.close();

Run it with node capture-shops.mjs. The script records HTTP status and final URL when navigation returns a response; an HTTP error status does not necessarily prevent a screenshot, so inspect the results and image before deciding whether to keep it.

Readiness choices

  • domcontentloaded waits for the initial document parse and is a useful starting point when shops load analytics or other long-lived requests.
  • load waits for the page load event, which can take longer when pages have many assets.
  • networkidle can be useful for pages that finish loading cleanly, but analytics, chat, and other persistent network activity may prevent the page from becoming idle.
  • Waiting for a known selector is often more targeted when the page has a stable landmark. Selectors vary by site, so use per-site overrides only when needed.
  • A short fixed delay is simple but does not prove that the important content has loaded. Keep it bounded and check representative results.

3. Capture full pages or individual elements

Change the screenshot call according to the thumbnail’s purpose. A full-page capture includes the entire scrollable page; a locator capture targets an element that can be selected reliably.

// Full scrollable page
await page.screenshot({ path: filename, fullPage: true, type: 'png' });

// A selected region; replace the selector with one verified for this site
await page.locator('main .hero').screenshot({ path: filename, type: 'png' });

For an element that is not initially visible, Playwright’s locator screenshot scrolls it into view. A tall, internally scrollable element does not automatically become a full-page image of all its internal content; the screenshot covers its visible content. Treat selectors as site-specific and expect markup changes to break them.

4. Choose format, scale, and thumbnail dimensions

Playwright can write a screenshot to a file or return image bytes for further processing. Use PNG when lossless detail or transparency matters, JPEG for compatible opaque photographic imagery, and WebP when your publishing pipeline supports it and smaller files are useful. There is no universal best card dimension or quality setting; choose dimensions based on the destination and inspect at final display size.

  • Viewport: set the same width and height for each capture. This is the visible browser area, not the final thumbnail dimensions.
  • Scale: scale: 'css' produces one output pixel per CSS pixel. scale: 'device' uses device pixels and may produce larger images on high-DPI displays. For compact, consistent output, CSS scale is a reasonable starting point.
  • Quality: JPEG and WebP support a quality setting; PNG does not use that setting. Compare file size and appearance at the actual card size before choosing a value.
  • Crop and resize: normalize all images to the same aspect ratio and dimensions after capture. A crop can hide important content, so review the framing across the set.

Playwright’s screenshot options are documented in the Page API and API parameter reference. Its screenshots guide describes viewport, full-page, and element captures.

5. Process a batch and review it

  1. Validate that every entry has a unique identifier and a parseable HTTP or HTTPS URL.
  2. Capture shops individually and retain the source URL, final URL, status, outcome, and output filename.
  3. Normalize the images to the card ratio and dimensions required by your site.
  4. Review a sample first, then review every image for cookie dialogs, popups, chat widgets, blank pages, inconsistent framing, and content changes.
  5. Retry only failures or captures that need correction, rather than silently treating every output as successful.
  6. Check each shop’s terms and the rights applicable to your intended publication and location before republishing screenshots, logos, or images.

For a large list, limit concurrency to avoid opening too many browser pages at once. Start with sequential processing as in the example, then raise concurrency gradually while keeping per-URL timeouts and result records. Browser work uses memory and network resources; a burst of simultaneous pages can make the machine unstable or cause sites to respond differently.

6. Troubleshoot common capture problems

Symptom Likely cause Fix
Navigation times out Slow shop, stalled asset, or long-running request. Use a bounded timeout, choose an appropriate readiness condition such as domcontentloaded, and retry selectively. Check whether the resulting page is usable before saving it as a final thumbnail.
Image is blank or incomplete Client-side content has not rendered, a page failed, or access was blocked. Wait for a meaningful visible selector or a short bounded delay, then inspect the page and recorded status. Do not assume a successful navigation means useful page content.
Cookie dialog or popup covers the shop The site presents a consent dialog, newsletter prompt, or chat overlay. Review whether the dialog must be accepted or can be dismissed under the site’s terms. A consistent capture policy matters; document exceptions instead of editing only selected images without recording it.
Thumbnails have different apparent sizes Viewport, device scale, or post-capture crop differs. Set the same viewport and scale, then normalize aspect ratio and output dimensions.
Element selector fails The selector does not exist on that shop or its markup changed. Check the page’s current structure, wait for the selector only when appropriate, and fall back to a viewport capture if no stable target exists.
One bad URL interrupts the batch An exception is not isolated per shop. Catch errors per URL and write an outcome record, as in the example. Keep the remaining captures running.
Output files are unexpectedly large Device-pixel scale, full-page captures, or lossless output creates more pixels or detail. Use CSS scale for a smaller baseline, capture the viewport when suitable, and compare JPEG or WebP quality at display size.

7. Performance, reliability, and cost

Local Playwright avoids a per-capture API charge, but you need to install and maintain a browser runtime and provide compute, memory, and network access. Sequential captures are slower but easier to diagnose; bounded concurrency can improve throughput while increasing resource use. Timeouts, per-shop records, selective retries, and a saved URL list make a batch easier to resume and audit.

A hosted screenshot API can remove much of the browser setup and maintenance. Compare its supported capture controls, output formats, handling of failed pages, and pricing against your workload before choosing. The examples below use ScreenshotNeo; its response includes page-verdict and billing headers, and its documented behavior is described in its API documentation.

Or skip the browser setup

One GET request can return an image for a shop URL. Replace the example target with a URL from your list and your API key. The service supports PNG, JPEG, WebP, and PDF; set the relevant request options as described in the ScreenshotNeo documentation.

cURL

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

Python

import requests

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

Node.js

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}`);
await Bun.write('shot.webp', res);

In a Node.js environment without Bun, save the response body with Node’s filesystem APIs:

import { writeFile } from 'node:fs/promises';

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}`);
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo can accept cookie consent banners like a visitor and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of these steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response indicates the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

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

Frequently asked questions

Should I capture shop homepages or product pages?

Use the page that best represents the shop in your directory. Homepages make sense for a shop list; use a product or collection page only when the listing is specifically about those products or collections.

Does taking a screenshot give me permission to publish it?

No. Capture documentation describes how to create an image, not whether you may republish the site, logo, or imagery. Check the relevant terms and rights for your intended use.

Should I keep the original screenshots?

Keeping originals alongside normalized thumbnails can make it easier to recrop cards later. Retain the source URL and capture outcome so each image can be traced to its shop.