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
Competitor prices — Audio & Small appliances
| Product | You | Voltbay | Harborline | PrimeDeck | Casa & Kin | Belmont Direct | Position |
|---|---|---|---|---|---|---|---|
| Aurora H7 Noise-Cancelling HeadphonesNL-1000 | $249.00 | $254.75 | $257.46 | $263.24 | $260.16 | — | Lowest |
| Aurora H7 Noise-Cancelling Headphones — BlackNL-1007 | $275.83 | $255.57 | $295.14 | $273.40 | $261.36 | $274.54 | Above |
| Aurora H7 Noise-Cancelling Headphones — Midnight BlueNL-1014 | $275.90 | $243.97 | $299.78 | $294.42 | $280.25 | $272.53 | Above |
| Aurora H7 Noise-Cancelling Headphones — SandNL-1021 | $273.31 | $265.33 | $265.90 | $247.33 | $259.81 | $288.91 | Above |
| Aurora Buds ProNL-1028 | $149.00 | $162.74 | $169.34 | $159.11 | $167.18 | $154.28 | Lowest |
| Aurora Buds Pro — BlackNL-1035 | $165.68 | $165.93 | $187.17OOS | $173.29 | $174.95 | $177.90 | Matched |
| Aurora Buds Pro — Midnight BlueNL-1042 | $165.19 | $148.24 | $183.85 | $161.72 | $162.17 | $167.73 | Above |
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.
| Layer | What it reads | Cost per page | How it fails | Verdict |
|---|---|---|---|---|
| 1. schema.org Product JSON-LD | The retailer's own offer block, published for search engines | One HTTP request and a JSON parse | Not published, published as a price range, or served stale from cache | Cheapest and most stable. Always try it first |
| 2. Per-domain extraction recipe | Named nodes in the server-rendered HTML, plus that domain's parsing rules | One HTTP request and a DOM query | Any layout change moves the node, and the selector still matches something nearby | Cheap to run, expensive to own. Maintenance lives here |
| 3. Rendered-page extraction | The DOM after the page's JavaScript has executed | A browser process and the full asset graph. Seconds, not milliseconds | Rarely misses a price. Fails on throughput, memory and bot challenges | Right 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.
| Failure mode | What actually happens | What a resilient system does |
|---|---|---|
| Layout change | A 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 component | The 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 variants | The 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 templates | The 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 cart | Some 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 200 | A 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 delisting | A 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 drift | A 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
| Source | Method | Coverage | Last full pass | Status |
|---|---|---|---|---|
| Voltbay | Structured data | 100% | 38 min ago | Healthy |
| Harborline | Domain recipe | 98.4% | 1 h 12 m ago | Self-healed Recipe regenerated 3 h ago after a layout change |
| PrimeDeck | Marketplace API | 99.6% | 22 min ago | Healthy |
| Casa & Kin | Domain recipe | 96.1% | 2 h 04 m ago | Degraded 12 URLs returning 404 — queued for re-discovery |
| Belmont Direct | Structured data | 100% | 51 min ago | Healthy |
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
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.
| Cost line | Build in-house | Buy a platform |
|---|---|---|
| First working scraper | Days. Genuinely easy, which is why build decisions get made here | Minutes. Paste competitor URLs, or discover listings by identifier |
| Domains 2 through 50 | One recipe, one set of locale quirks and one owner per domain. Effort is linear and endless | Included. New domains cost no engineering time |
| Rendering and access | A headless browser fleet with memory ceilings and queueing, plus per-domain rate control | Included, with collection rate tuned per source |
| Breakage detection | The line that gets cut. Without it you learn from a bad number in a board report | Coverage and freshness dashboard, plus alerts when a source degrades |
| Product matching | Underestimated by more than the scraping. Identifier join, then title embeddings, then images | Automatic, with confidence scores and a review queue |
| Screenshot evidence | A second rendering pipeline, plus storage and a retention policy | Timestamped capture on every below-MAP finding |
| Ongoing ownership | An engineer interrupted every time a competitor ships a redesign | Reviewing 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.
Is price scraping legal?
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.
Keep reading
- How to scrape competitor pricesThe step-by-step method, including selecting sources and setting refresh intervals.
- Is web scraping legal?The legal questions that actually carry risk, explained without scare tactics.
- Competitor price monitoringWhat sits on top of collection: matching, alerting and history.
- Data quality moduleCoverage and freshness dashboards, self-healing recipes and degradation alerts.
- Product matching guideWhy a correct price on the wrong product is the most expensive error in the stack.
- API and webhooksResponse shapes, event types and export options, included on every plan.
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.