ScreenshotNeo

BlogHow-to

How to Capture Product Screenshots and Prices in Headless Chrome

Capture a product page in headless Chrome, wait for its price, and save both visual and structured evidence with the right context.

By the ScreenshotNeo team4 October 20267 min read

To capture a product page and its price in headless Chrome, capture the rendered page and extract the price as separate pieces of evidence. A screenshot stores pixels; it does not reliably provide machine-readable price data. Wait for the product and price to appear, save a viewport or element screenshot, then record the visible price and any structured offer data with its currency, product or variant, label, URL, and capture time.

1. Capture a page with Chrome Headless

For a one-off viewport screenshot, use Chrome’s command-line screenshot option. Set the viewport to the layout you want to inspect:

chrome --headless --screenshot --window-size=412,892 https://example.com/product

Chrome writes screenshot.png in the current directory by default. Change the viewport dimensions to capture another layout. The rendered page can differ by viewport, so keep those dimensions with the resulting evidence. Chrome’s --timeout sets a maximum wait for screenshot and DOM-dump operations; --virtual-time-budget can fast-forward time-dependent page code. Neither option confirms that a particular product price has loaded. See the Chrome Headless documentation.

2. Use Puppeteer when you need waits or element capture

A script gives you control over navigation, selector-based readiness, extraction, and whether to capture the full page or a particular product element. Install Puppeteer in a project with Node.js:

npm install puppeteer

Save this as capture-product.mjs. Replace the example URL and selectors with those used by the page you are collecting. The price selector shown is illustrative; retailer pages do not share a universal selector.

import puppeteer from 'puppeteer';

const productUrl = process.env.PRODUCT_URL ?? 'https://example.com/product';
const productSelector = process.env.PRODUCT_SELECTOR ?? '[data-product]';
const priceSelector = process.env.PRICE_SELECTOR ?? '[data-price]';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
  await page.goto(productUrl, { waitUntil: 'networkidle2', timeout: 60000 });

  // A selector wait is a page-specific readiness check; navigation alone is not.
  await page.waitForSelector(productSelector, { visible: true, timeout: 15000 });
  await page.waitForSelector(priceSelector, { visible: true, timeout: 15000 });

  const product = await page.$eval(productSelector, (el) => el.innerText.trim());
  const visiblePrice = await page.$eval(priceSelector, (el) => el.innerText.trim());
  await page.screenshot({ path: 'product.png', fullPage: true });

  const record = {
    url: page.url(),
    capturedAt: new Date().toISOString(),
    viewport: { width: 1280, height: 900 },
    product,
    visiblePrice,
    screenshot: 'product.png'
  };
  process.stdout.write(JSON.stringify(record, null, 2) + '\n');
} finally {
  await browser.close();
}

Run it with your page and selectors:

PRODUCT_URL='https://example.com/product' PRODUCT_SELECTOR='.product-detail' PRICE_SELECTOR='.current-price' node capture-product.mjs

Puppeteer’s Page.screenshot() captures a page, and an element handle’s screenshot() captures a selected element. For an element shot, replace the page screenshot call with:

const productElement = await page.$(productSelector);
if (!productElement) throw new Error(`Product not found: ${productSelector}`);
await productElement.screenshot({ path: 'product-element.png' });

Puppeteer scrolls an element into view as needed for an element screenshot. Choose a selector that identifies the intended product or offer, particularly on pages with recommendations or multiple variants. See the Puppeteer screenshot guide.

3. Extract a price without confusing offers

Read the rendered price the page presents to the shopper, and inspect structured product data when it exists. Google’s product data guidance includes Offer.price or a nested priceSpecification.price, along with priceCurrency. Structured data is published by the page to describe its content; it is not independent confirmation of the price rendered at capture time. Compare the candidate structured value with the visible price and retain mismatches for review.

Do not select the first number on the page. A product can show a current price, a struck-through former price, a member price, several offers, or prices for different variants. Preserve the label and selected variant so the amount remains interpretable. Google distinguishes active, strikethrough, and member pricing in its merchant listing documentation and describes product offers in its product structured data documentation.

When investigating content populated by JavaScript, Chrome’s --dump-dom outputs serialized DOM after parsing and script execution. That is different from retrieving the original source HTML and can reveal client-rendered content. It still does not tell you which of several prices is the active shopper price; inspect the relevant element and its context. See Chrome Headless documentation.

4. Save the screenshot and price as one evidence record

Store the image and a structured record together. A useful minimum record includes:

  • Product URL and capture timestamp.
  • Product name or identifier, selected variant, and offer context.
  • Visible price text, normalized numeric value if parsed, currency, and label such as sale or member price.
  • Screenshot filename, viewport dimensions, and whether it is a full-page or element capture.
  • Structured-data price and currency, when available, plus whether they agreed with the visible price.

This makes later review possible if a sale ends or a product page changes. If the page has multiple offers or variants, capture the selected state and keep each price associated with its own context instead of collapsing them into a single unexplained amount.

5. Choose the capture method for the job

Method Useful for Limit
Chrome CLI A quick viewport screenshot or rendered DOM inspection. Page-specific waits and extraction logic are limited; timing flags do not establish price readiness.
Puppeteer Selector waits, scripted extraction, and page or element screenshots. Requires browser setup and selectors that fit the target page.
Visible DOM price Recording the price displayed in the captured state. Page markup varies; text can include labels or multiple offers.
Structured Offer data Machine-readable candidate price and currency. May be absent or differ from the visible current offer; validate it.

6. Troubleshooting

Symptom Likely cause Fix
Screenshot shows a loading state or missing price The capture happened before client-side content appeared. Wait for a visible product and price selector with a bounded timeout. A fixed delay or navigation event alone is not proof of readiness.
Selector wait times out The selector is wrong, the product is unavailable, a variant must be selected, or the page did not reach the expected state. Inspect the rendered DOM, verify the selector for this page, and fail the capture clearly if the expected product is absent.
Captured price is crossed out or for the wrong variant A broad selector matched a former price, member offer, recommendation, or another variant. Target the active offer within the selected product and record the price label and variant context.
Structured price and visible price disagree Markup may describe another offer or state, or the visible page may have changed after markup was produced. Keep both values with their sources, currency, and capture time; flag the discrepancy instead of silently choosing one.
CLI screenshot does not include the whole page A normal screenshot captures the viewport. Use Puppeteer with fullPage: true when a full-page image is needed.
Page never becomes network-idle Persistent connections or ongoing requests can prevent an idle condition. Use an appropriate navigation wait and then wait for the specific product and price selectors. Bound each wait so failures are visible.

7. Performance, reliability, and cost

Browser startup, page loading, and third-party resources dominate a capture workflow. Reuse a browser process for batches of pages where appropriate, close pages and browsers in cleanup paths, and set explicit navigation and selector timeouts. Capture only the element you need when a full page is unnecessary. Keep the viewport, wait condition, URL, and timestamp with each result so repeated captures can be compared meaningfully.

Retailer pages can change selectors, offer labels, and variant behavior. Treat missing selectors, empty price text, and currency ambiguity as explicit capture failures or review cases. A screenshot and structured value are evidence of a particular captured state, not a guarantee that a price remains current afterward. The research references establish no universal selector, retailer-specific price correctness, or benchmark for capture cost or speed; those depend on the target site and deployment.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its one-call endpoint returns an image or PDF; the call below requests a WebP screenshot of the example product page. See the ScreenshotNeo API documentation.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/product"},
    timeout=90,
)
open("product.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/product'
});
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('product.webp', res);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. ScreenshotNeo captures the page image; use your own page-specific extraction when you need a machine-readable product price, and compare it with what the screenshot shows.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

Does a screenshot contain the product price as text?

No. It contains rendered pixels. Extract text separately from the rendered page or structured data, then associate that value with the image.

Is structured product data always available?

No. Structured data varies by page, and supported types are not universal. Treat it as an optional candidate source and validate it against the visible offer. See Google’s structured data introduction.

Should I use a viewport or full-page screenshot?

Use a viewport capture to preserve a particular screen layout, or a full-page capture when the entire product detail is relevant. Record which one you chose because the images represent different evidence.