Skip to content
Price IntelligencePrice Intelligence home

Solution

Price scraping: the extraction stack, and what breaks it

Price scraping is the automated extraction of product prices from public web pages, usually competitors' storefronts and marketplace listings. A reliable price scraper reads schema.org Product structured data first, falls back to a per-domain extraction recipe, and validates every value before storing it. Price Intelligence runs that stack and repairs it when a site changes.

  • schema.org structured data read first, extraction recipes as fallback
  • Layout changes detected, recipes regenerated automatically
  • Every price validated before it is stored
Start free trialOpen the live demo14 days, no credit card, cancel in one click.

Competitor prices — Audio & Small appliances

ProductYouVoltbayHarborlinePrimeDeckCasa & KinBelmont DirectPosition
Aurora H7 Noise-Cancelling HeadphonesNL-1000$249.00$254.75$257.46$263.24$260.16Lowest
Aurora H7 Noise-Cancelling Headphones — BlackNL-1007$275.83$255.57$295.14$273.40$261.36$274.54Above
Aurora H7 Noise-Cancelling Headphones — Midnight BlueNL-1014$275.90$243.97$299.78$294.42$280.25$272.53Above
Aurora H7 Noise-Cancelling Headphones — SandNL-1021$273.31$265.33$265.90$247.33$259.81$288.91Above
Aurora Buds ProNL-1028$149.00$162.74$169.34$159.11$167.18$154.28Lowest
Aurora Buds Pro — BlackNL-1035$165.68$165.93$187.17OOS$173.29$174.95$177.90Matched
Aurora Buds Pro — Midnight BlueNL-1042$165.19$148.24$183.85$161.72$162.17$167.73Above

Out-of-stock listings are struck through and excluded from position and repricing decisions.

Price scraping is the automated collection of prices from public product pages. The stack runs in three layers: read the retailer's own schema.org Product structured data where it exists, fall back to a per-domain extraction recipe against server-rendered HTML, and render the page in a browser only when nothing cheaper works. Each layer down costs more and fails differently. Almost none of the work is the first scrape. It is noticing the day one stops being true.

How price scraping works, layer by layer

Every price scraper does the same job: turn a URL into a number, a currency and a stock status you would bet a margin decision on. There are three routes, and the difference is not accuracy. All three work when the page cooperates. The difference is cost per page, and behavior the week a retailer ships a redesign.

The three extraction layers, in the order a sane system tries them
LayerWhat it readsCost per pageHow it failsVerdict
1. schema.org Product JSON-LDThe retailer's own offer block, published for search enginesOne HTTP request and a JSON parseNot published, published as a price range, or served stale from cacheCheapest and most stable. Always try it first
2. Per-domain extraction recipeNamed nodes in the server-rendered HTML, plus that domain's parsing rulesOne HTTP request and a DOM queryAny layout change moves the node, and the selector still matches something nearbyCheap to run, expensive to own. Maintenance lives here
3. Rendered-page extractionThe DOM after the page's JavaScript has executedA browser process and the full asset graph. Seconds, not millisecondsRarely misses a price. Fails on throughput, memory and bot challengesRight for a minority of domains. Wrong as a default

Ordering matters because of arithmetic. Ten thousand products checked four times a day is forty thousand fetches. At layer 1 that is a rounding error. At layer 3 it is a fleet.

Layer 1: schema.org Product JSON-LD

Most retailers hand a machine-readable price to search engines voluntarily, because rich results depend on it. That block is the best source you can read, for one reason: the retailer maintains it. Nobody redesigns a page and forgets to update the price they publish to Google.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "18V Brushless Drill Kit, 2 x 4.0Ah",
  "sku": "NL-4412",
  "gtin13": "0812345678903",
  "offers": {
    "@type": "Offer",
    "url": "https://example.com/products/nl-4412",
    "priceCurrency": "USD",
    "price": "189.00",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
</script>

The field you want is offers.price: a plain decimal string, no currency symbol, no thousands separator, nothing to unpick. Currency sits beside it in priceCurrency. Availability is a URL enum rather than a boolean, so InStock, OutOfStock, PreOrder and BackOrder arrive distinct instead of as a guess about a greyed-out button. And gtin13 is why structured data doubles as the matching key.

  • Variants are the first trap. A product with sizes or capacities often publishes an AggregateOffer with lowPrice and highPrice instead of a single Offer. Reading lowPrice as the price reports the cheapest variant, systematically below what a shopper pays, and the error is invisible because the number is real.
  • Two offers, one page. Some templates emit list price and sale price as separate Offer entries. Take the lowest currently valid one, not the first in the array.
  • Markup can be stale. A cached block can carry yesterday's price while the visible page shows today's. Read structured data first, cross-check the rendered page, and flag disagreements past tolerance.
  • Absence is information. A domain that stops publishing structured data has changed platform or theme, so the recipe underneath is about to be wrong too.

Layer 2: per-domain extraction recipes

When a site publishes nothing usable, you write a recipe for that domain. A recipe is not one CSS selector. It is a small versioned config: which URL shapes are product pages, which node holds the shelf price, which holds the crossed-out list price so the two are never confused, how the locale formats decimals, and what a valid product page looks like at all. Recipes are cheap to run and expensive to keep true. Your first scraper is a weekend. Owning fifty is a job.

Layer 3: rendered-page extraction

Some prices only exist after JavaScript runs. For those domains you launch a browser, let the page hydrate, wait for the price node to populate rather than for an arbitrary timeout, and read the DOM. It works, and it costs a browser process plus the whole asset graph per fetch. Rendered extraction is a per-domain promotion, decided deliberately, not a default applied everywhere.

How competitor price scraping breaks, and what a resilient system does

Price feeds rarely fail loudly. They fail by delivering numbers that are no longer true, on schedule, into a dashboard that looks healthy. A vendor who cannot describe these eight failures has not met them.

Eight ways competitor price scraping breaks
Failure modeWhat actually happensWhat a resilient system does
Layout changeA theme update nests the price one level deeper. The selector still matches something, usually the crossed-out list price beside it. Every number is high, and on time.Fingerprint page structure on every collection, not just the price. When the hash changes, regenerate the recipe and re-validate against structured data first.
Price moved into a client-rendered componentThe server returns an empty price container that a script fills after hydration. A static fetch stores null; a careless one carries yesterday's value forward forever.Promote that domain to rendered extraction. A null describes the recipe, not the product, and must never become a stored price.
Currency and locale variantsThe same URL serves EUR to one IP and USD to another, and writes 1.299,00 where you expect 1,299.00. A US float parse turns that into 1.299, so a EUR 1,299.00 product enters your database at about one euro thirty.Pin geography and currency per source, read priceCurrency rather than infer it from a symbol, and convert at report time so FX cannot rewrite history.
A/B tested templatesThe retailer serves two templates on one URL and your recipe fits one. Collection succeeds Monday, fails Tuesday, succeeds Wednesday. It reads as flakiness, so it gets retried, not fixed.Treat a domain as a set of templates. Keep a recipe per signature, route each response to the one that matches, and alert on an unseen signature rather than failing open.
Price only visible in cartSome listings publish no price and require an add-to-cart action to reveal it. A scraper reading missing as zero poisons every average that product touches.Record it as price-not-published, never as zero. For a brand that is itself a finding: hiding price until checkout is a common way to sell below MAP. See MAP monitoring.
Bot challenges that return HTTP 200A challenge or rate-limit page returns HTTP 200 with no product in the body. Anything keying on the status line records a successful fetch.Classify the response body before extraction: product, challenge, error or unknown. Back off per domain, and mark a source you cannot collect politely as degraded.
URL churn and delistingA retailer moves /products/oak-patio-chair to /p/48812, or delists it. The old URL 404s, or redirects to a category page that parses cleanly and is full of other prices.Re-discover the match by identifier rather than URL, and keep moved, delisted and out-of-stock distinct. A delisted competitor leaves the market average.
Variant and pack-size driftA competitor switches the same URL from a single unit to a two-pack. The price nearly doubles and reads as an increase. Nothing broke; the product changed.Re-verify identifier, title tokens and pack count on every collection, not only at match time. A new multipack token drops confidence and routes the pair to review.

The common thread: each one produces a plausible number rather than an error. That is why detection has to be a feature and not a log line.

The failure nobody instruments: quiet success

Most monitoring watches for jobs that fail. The expensive failure is a job that succeeds forever with a value that stopped changing. If a competitor's price on a volatile SKU has been identical for forty days while three rivals moved twice, the explanation is rarely pricing discipline. It is a static element, a cached page or the wrong node. Staleness alerting has to fire on values that stopped moving, not only on requests that stopped returning. Almost nobody writes that query.

Data quality — source health

Coverage

98.8%

of tracked URLs

Checks today

1,106

Auto-healed (7d)

7

no data gaps

Human queue

2

awaiting review

SourceMethodCoverageLast full passStatus
VoltbayStructured data100%38 min agoHealthy
HarborlineDomain recipe98.4%1 h 12 m agoSelf-healed

Recipe regenerated 3 h ago after a layout change

PrimeDeckMarketplace API99.6%22 min agoHealthy
Casa & KinDomain recipe96.1%2 h 04 m agoDegraded

12 URLs returning 404 — queued for re-discovery

Belmont DirectStructured data100%51 min agoHealthy

Incident log

  • Today 04:12HarborlinePrice selector returned null on 214 URLs.New extraction recipe generated and validated — live 04:19
  • Today 01:50Casa & Kin12 product URLs returned 404.Re-discovery queued via GTIN lookup
  • Yesterday 19:31PrimeDeckRate limit reached.Backed off and completed the cycle 22 min late
Per-source coverage and freshness in the Northline Supply demo tenant, including a domain flagged as degraded before it produced a wrong number.
How a competitor price is extracted and validatedA product page is fetched, then extraction is attempted in three orders of preference: schema.org structured data, then a per-domain recipe, then full page rendering. Every extracted value passes a plausibility check before it is stored. A failed check regenerates the domain recipe and, if that also fails, raises a human review task and alerts the customer.Fetch pagerate limitedTRIED IN ORDER — CHEAPEST AND MOST ROBUST FIRST1 · Structured dataschema.org Product JSON-LD2 · Domain recipeselectors written once per site3 · Rendered pageslowest, used only when neededPlausible?currency · magnitude · stockpassStoredappend-onlyfailRegenerate recipe, revalidateHuman queue + alert
Extraction runs cheapest-first. Structured data where a site publishes it, a per-domain recipe where it does not, and full rendering only when neither works. Everything passes a plausibility check before it is stored.

Validation: the gap between a number and a price

Extraction produces candidate values. Validation decides which are allowed to exist. This runs before storage, not in a report afterwards.

  • Digit-shift detection. A value at roughly ten times or one tenth of the trailing median is a parse error far more often than a promotion. $18.99, $189.90 and $1,899.00 are one broken selector three ways.
  • Range check against the product's own history. Compare to the trailing median at that source, not a global rule. A 40 percent overnight move on a mattress is suspicious; on a graphics card at launch it is Tuesday.
  • Zero, negative and null rejection. A price of $0.00 is never a price. It is a challenge page, an empty container, or a shipping field read by mistake.
  • Currency agreement. The value must carry a currency, and it must be the one that source is pinned to. A silent switch from GBP to USD stops the pipeline.
  • Cross-layer agreement. Where structured data and the rendered page both yield a price, they should agree. Persistent disagreement means the recipe reads the wrong node.
  • Freshness and confidence gates. Anything older than its refresh interval is labeled stale, and repricing rules refuse to act on it. Low-confidence matches never reach a pricing decision, because a correct price on the wrong product is still wrong. See the product matching guide.

Price scraping software versus building it yourself

This gets decided after someone builds a prototype in two days and concludes the whole thing is two weeks. The prototype is real. The estimate is wrong, because the prototype covered the part that never needs maintaining.

Build versus buy, by cost line
Cost lineBuild in-houseBuy a platform
First working scraperDays. Genuinely easy, which is why build decisions get made hereMinutes. Paste competitor URLs, or discover listings by identifier
Domains 2 through 50One recipe, one set of locale quirks and one owner per domain. Effort is linear and endlessIncluded. New domains cost no engineering time
Rendering and accessA headless browser fleet with memory ceilings and queueing, plus per-domain rate controlIncluded, with collection rate tuned per source
Breakage detectionThe line that gets cut. Without it you learn from a bad number in a board reportCoverage and freshness dashboard, plus alerts when a source degrades
Product matchingUnderestimated by more than the scraping. Identifier join, then title embeddings, then imagesAutomatic, with confidence scores and a review queue
Screenshot evidenceA second rendering pipeline, plus storage and a retention policyTimestamped capture on every below-MAP finding
Ongoing ownershipAn engineer interrupted every time a competitor ships a redesignReviewing exceptions

Illustrative comparison of where effort goes, not a quote. Put your own rates into the next paragraph.

Worked example, with numbers you should replace. Suppose a fully loaded engineer-day costs $800, and the build lands at 15 engineer-days, optimistic beyond a handful of domains. That is $12,000 before a single price is collected. Suppose maintenance runs one day a month: $9,600 a year, plus proxies and compute. Against $299 a month for 2,500 products at twice-daily refresh, which is $3,588 a year, the build has to return roughly $18,000 in year one. Sometimes it does. More often nobody costed maintenance at all.

Build it yourself when

  • Scraping is your product, not an input to a decision. If you sell the data, own the pipeline.
  • You need under ten domains, they all publish structured data, and one engineer wants the job.
  • The source sits behind credentials only you should hold, such as a distributor portal.
  • A compliance requirement means collected data cannot leave your own infrastructure.

Buy when

  • Coverage matters more than control: dozens of storefronts and marketplaces, not five.
  • Matching is as hard as extraction, and it is not a scraping problem.
  • You need evidence, not just numbers: screenshots that survive a conversation with a reseller.
  • Collection has to feed competitor price monitoring, alerting and repricing without you building three more systems.

What a price scraping tool has to hand back

Collected prices are only useful if they leave the tool cleanly. Every observation carries its timestamp, source URL, currency, stock status, the marketplace seller where there is one, and the match confidence that tied it to your SKU. That is what makes an argument about a number resolvable months later. Access is by REST API, webhooks on price and stock change, scheduled CSV or SFTP export, and direct pushes into connected stores. The API and webhooks are on every plan, because gating data access behind a higher tier holds your own data hostage.

REST API — offers for one product

Request

curl https://api.priceintelligence.io/v1/products/NL-1084/offers \
  -H "Authorization: Bearer $PI_API_KEY"

Response

{
  "sku": "NL-1084",
  "gtin": "00910042852",
  "your_price": 199.00,
  "currency": "USD",
  "price_index": 101.4,
  "position": "above",
  "checked_at": "2026-07-24T06:12:04Z",
  "offers": [
    { "competitor": "Voltbay",    "price": 194.99, "in_stock": true,  "confidence": 0.99 },
    { "competitor": "Harborline", "price": 201.50, "in_stock": true,  "confidence": 0.97 },
    { "competitor": "PrimeDeck",  "price": 189.00, "in_stock": false, "confidence": 0.93 }
  ]
}

API access and webhooks are included on every plan. There is no percentage surcharge for calling your own data.

A live API response for one demo product: matched competitor offers with price, currency, stock, seller, capture timestamp and match confidence.

The short version: collecting prices a retailer publishes openly on a public product page is ordinary competitive research, and buyers have walked into rival stores with a clipboard for a century. The questions that carry real risk are narrower than the debate suggests, and they concern method and use rather than scraping as such.

Our position, plainly: we collect public listing pages at a rate sources can serve, we do not authenticate into sites on your behalf, and a source that cannot be collected reliably and politely is marked degraded rather than guessed at. For the end-to-end method, see how to scrape competitor prices.

$99/mo

Starter: 500 products, daily refresh, 10 competitor URLs per product

Hourly

Fastest collection interval, available on Enterprise

14 days

Free trial, no credit card required

Point it at the domains you already gave up on

Connect a store, add the competitors that broke your last scraper, and see what comes back. Fourteen days, no credit card.

Frequently asked questions

What is price scraping?

Price scraping is the automated extraction of prices, stock status and promotions from public product pages, usually competitors' storefronts and marketplace listings. A price scraper fetches the page, reads the price from structured data or from named nodes in the HTML, validates the value, and stores it with a timestamp and source URL so the observation can be audited later.

Is price scraping legal?

Collecting prices a retailer publishes openly on public pages is ordinary competitive research. Risk concentrates in narrower questions: whether you bypass access controls or logins, whether your collection rate burdens the site, whether personal data is involved, and whether the data is used to set your own price rather than to coordinate with a competitor. This is educational information, not legal advice.

How do you scrape prices from a JavaScript site?

First check whether the site publishes schema.org Product structured data, because many client-rendered storefronts still emit it server-side for search engines, which avoids rendering entirely. If it does not, the page has to be loaded in a browser and read after hydration, waiting for the price node to populate rather than for a fixed timeout. Rendering is a per-domain decision because it costs far more per page.

What is the difference between price scraping and price monitoring?

Price scraping is the collection step: turning a URL into a validated price. Price monitoring is the system around it, which includes matching each competitor listing to the right product in your catalogue, scheduling refreshes, detecting when a source degrades, alerting the right person and keeping history. Scraping without matching produces numbers that are accurate about the wrong product.

Should I build my own price scraper or buy price scraping software?

Build it when scraping is your product, when you need fewer than about ten domains that all publish structured data, or when the source sits behind credentials only you should hold. Buy it when coverage across many domains matters, when product matching is as hard as extraction, when you need screenshot evidence, or when nobody wants to be on call for competitor redesigns.

What happens when a competitor blocks the scraper?

Challenge pages frequently return HTTP 200 with no product content, so responses are classified by body rather than by status code. Collection backs off to a rate the source can serve. If a source still cannot be collected reliably and politely, it is reported as degraded on the coverage dashboard and you are alerted, rather than a guessed or carried-forward value entering your data.

How often can competitor prices be scraped?

Refresh runs daily on Starter, twice daily on Growth, four times daily on Scale and hourly on Enterprise. Match the interval to category volatility and to how quickly you can act. Collecting four times a day and approving price changes once a week wastes both ends of the pipeline.

Can I get scraped price data through an API?

Yes. A REST API, webhooks on price and stock changes, and scheduled CSV or SFTP export are included on every plan including Starter. Each observation carries its timestamp, source URL, currency, stock status, marketplace seller where applicable, and the match confidence that tied it to your product.

Start monitoring in the next ten minutes

Connect your store, match your catalogue and get your first competitor comparison in the same session.

14 days, no credit card, cancel in one click.