← ALL FIELD NOTES

How to Extract Prices and Availability Without Confusing Display Text with Data

Build reliable ecommerce extraction by treating price and stock as versioned offer data: preserve display text, normalize only supported fields, and flag ambiguity.

Price and availability look simple until a product page contains $99.00, From $79, Member price, a crossed-out list price, and Ships in 2 weeks at the same time. A scraper that captures the first currency-looking string and nearby badge may produce output, but it has not necessarily identified a purchasable offer.

To extract price and availability from websites reliably, treat the task as semantic normalization with evidence, not as text collection. Your system should identify the offer and selected variant first, preserve the source presentation, then produce normalized fields only when the page supports that interpretation.

Start with an offer record, not two strings

A product page can represent multiple offers: different sizes, colors, sellers, fulfillment methods, currencies, and customer segments. Model the record around the offer that a buyer can actually select.

A practical schema might look like this:

{
  "product_id": "trail-runner",
  "variant_id": "trail-runner-blue-42",
  "selected_options": { "color": "Blue", "size": "42" },
  "price_type": "sale",
  "price": "119.99",
  "currency": "USD",
  "display_price": "$119.99",
  "list_price": "149.99",
  "display_list_price": "$149.99",
  "availability": "InStock",
  "availability_label": "In stock — ships in 1–2 business days",
  "availability_url": "https://schema.org/InStock",
  "observed_at": "2026-09-23T12:00:00Z",
  "source_layer": "json_ld",
  "evidence": {
    "selector": "script[type='application/ld+json']",
    "raw_offer": "{...}"
  },
  "warnings": []
}

The exact field names are your design choice. The important properties are separation and traceability:

  • price is a numeric decimal representation, not formatted text.
  • currency is an explicit code, not a symbol inferred from the page.
  • display_price retains what a visitor saw.
  • availability is a controlled state.
  • availability_label keeps qualifying operational language.
  • variant_id and selected options bind price and stock to the same purchasable item.
  • observed_at, source layer, and raw evidence make the record auditable later.

This mirrors the distinction in Schema.org’s Offer type: price, currency, availability, and validity are separate concepts. Schema.org also recommends a standard ISO 4217 currency value through priceCurrency rather than relying on a display symbol.

Use an evidence-first extraction order

A resilient extractor should collect several representations, but it should not give them equal authority. A useful order is:

  1. JSON-LD in script[type="application/ld+json"]
  2. Microdata, RDFa, and explicit machine-readable attributes
  3. Product-state data exposed in page-specific data-* attributes
  4. Rendered DOM text and purchase controls
  5. Accessibility labels as supporting context

JSON-LD is designed to be embedded in HTML and is commonly used for product offers. Google recommends JSON-LD where the site setup permits it because it is easier to maintain at scale, while the JSON-LD specification describes embedding it with application/ld+json. See Google’s structured data guidance and the JSON-LD specification.

This is not a rule that JSON-LD is always correct. Structured markup can be stale, generic, or represent a parent product instead of the selected variant. It is simply the best initial source when it supplies a complete, internally consistent offer.

When structured data is absent, data-* attributes can carry useful page-specific values. The HTML Standard’s custom data attribute guidance establishes these attributes as a mechanism for page-specific custom data. Treat an attribute such as data-price="119.99" plus data-currency="USD" as stronger evidence than parsing $119.99 from an unrelated recommendation card.

Preserve every source before choosing a canonical value

For each candidate, retain:

  • raw value and raw element or JSON path;
  • source layer and selector/path;
  • associated variant identifier or selected option state;
  • confidence and parsing warnings;
  • capture time.

Then select a canonical value through explicit rules. This makes disagreements visible rather than accidental. For example, if JSON-LD says 119.99 USD but the active variant’s purchase panel says 129.99 USD, emit a conflict. Do not silently choose whichever extractor ran last.

Price is not always one scalar

The fastest route to bad price data is forcing every presentation into one price field.

Single active price

A standard active offer can normalize cleanly when a numeric amount and explicit currency are available:

{
  "price_kind": "single",
  "price": "119.99",
  "currency": "USD",
  "display_price": "$119.99"
}

If you see both a sale price and a crossed-out reference price, store them separately. Do not overwrite the active transaction price with the list price merely because the list price is visually prominent.

Ranges and aggregate offers

From $79 to $119 is not a price of $79. It may describe variants, sellers, or configurations. Schema.org’s AggregateOffer type has lowPrice, highPrice, and offerCount specifically for multi-offer situations.

Represent the semantics directly:

{
  "price_kind": "range",
  "low_price": "79.00",
  "high_price": "119.00",
  "currency": "USD",
  "display_price": "From $79"
}

A range should remain a range until a particular variant or seller is selected. If your downstream API requires a scalar, make that transformation a documented consumer decision, not an extraction shortcut.

Complex or conditional pricing

Unit pricing, subscriptions, member pricing, installment plans, and coupons need their own structures. Google’s merchant listing documentation describes priceSpecification and UnitPriceSpecification for complex pricing, and notes a precedence rule when offers.price and a price specification both encode an active price.

For extraction, that means two things:

  1. Keep the plain active offer price distinct from conditions and price specifications.
  2. Flag contradictions instead of merging fields because they happen to look price-like.

For example, Member price $85 should not become the public price unless your record also captures the membership condition. A safe result may be price_kind: conditional with a raw label and a review warning.

Parse display text only with locale and currency context

Visible prices are designed for people in a particular locale, not for portable parsing. Currency formatting can change symbol placement, decimal precision, separators, spacing, and even include non-breaking or bidirectional characters. MDN’s Intl.NumberFormat reference documents these locale-dependent formatting behaviors.

That makes these strings ambiguous without context:

  • 1.299 could be one thousand two hundred ninety-nine or 1.299.
  • 1,299 could be one thousand two hundred ninety-nine or 1.299.
  • $99.00 does not independently establish USD.
  • 99 kr does not uniquely establish a currency code.

A conservative parser should:

  1. Normalize Unicode whitespace and control characters for parsing, while retaining original text.
  2. Obtain currency from a machine-readable field, site locale, or explicit page context before relying on a symbol.
  3. Determine separators from known locale or a consistent page-level format.
  4. Convert to a decimal representation without floating-point rounding.
  5. Mark the value unresolved when currency or decimal interpretation cannot be supported.

Do not guess a currency solely from $, €, or kr. An unresolved record is more useful than a confidently wrong one.

Build extraction records you can inspect, not just values you can export. Keep raw evidence, source paths, and warnings alongside normalized output. Create an account to try PagePith.

Availability needs a vocabulary and a raw label

Stock language is not binary. A controlled availability field enables filtering and comparison, but it cannot replace the merchant’s wording.

Schema.org’s Offer availability values include states such as InStock, OutOfStock, SoldOut, BackOrder, PreOrder, LimitedAvailability, InStoreOnly, OnlineOnly, and Discontinued. These make a useful target vocabulary.

A mapping layer can be intentionally narrow:

Page labelNormalized stateKeep as raw detail?
In stockInStockYes
Sold outSoldOutYes
Available for preorderPreOrderYes
Ships in 2 weeksPossibly BackOrder or unresolvedAlways
Notify me when availableUsually unavailable; do not assume permanent OutOfStockAlways
Limited stockLimitedAvailability when supported by contextYes

Avoid equating delivery estimates with inventory state automatically. “Ships in 2 weeks” could mean backorder, warehouse transfer, made-to-order production, or simply a conservative delivery promise. If the page does not state stock semantics, preserve the label and emit an unresolved availability state.

Bind price and availability to the selected variant

A parent product frequently has a starting price while each variant has its own stock state. One size may be sold out, another available, and a third priced differently. Google’s product variant guidance explicitly notes that variants can differ in price and availability, including pages where selection dynamically changes the values.

Your extraction flow should therefore be variant-aware:

  1. Identify variant dimensions: size, color, capacity, seller, condition, or fulfillment mode.
  2. Read the current selected state from controls or application data.
  3. Extract price and availability associated with that same state.
  4. Store an SKU, GTIN, variant URL, or stable option tuple where available.
  5. Repeat only for states you are actually able and permitted to select and observe.

Never combine a price from the parent product header with availability from a selected child variant unless the page establishes that they refer to the same offer.

Capture dynamic state and freshness

Modern product pages may place incomplete data in initial HTML and update the purchase panel after scripts run. Google cautions that JavaScript-generated product markup can make rapidly changing price and availability information less reliable for crawling; see its product snippet guidance.

For a monitoring pipeline, capture separately:

  • original response HTML;
  • rendered DOM after the page reaches a defined stable state;
  • selected options and relevant request or state payloads when available;
  • observation timestamp;
  • offer validity fields such as priceValidUntil, validFrom, or availabilityEnds when published.

An observed_at timestamp is not a claim that the merchant guarantees a value until that time. It only records when your system observed it. Validity fields and observation time answer different questions, so store both.

Validate before publishing a record

Make semantic ambiguity a first-class outcome. A record should be rejected or routed for review when it has any of the following conditions:

  • numeric price without a determinable currency;
  • range presentation collapsed into a scalar price;
  • multiple competing active prices with no documented precedence;
  • price and availability tied to different variants;
  • a conditional price with its condition omitted;
  • stale structured data that conflicts with the active purchase interface;
  • stock copy that cannot be mapped without guessing.

This validation strategy follows the same distinctions present in Google’s merchant listing documentation: active price, currency, availability, and variant identity are not interchangeable fields.

A small PagePith demonstration

The supplied PagePith proof shows a fetch-tier retrieval of https://schema.org/Offer. It returned the page title “Offer - Schema.org Type”, reported a content length of 205,615, and included a Markdown excerpt identifying Offer as a Schema.org type and describing it as an offer to transfer rights to an item or provide a service.

That is useful evidence of a source fetch: it shows that PagePith retrieved the referenced documentation in this instance. It is not evidence that PagePith rendered a JavaScript storefront, selected variants, extracted a merchant price, or verified inventory. Those steps require page- and workflow-specific evidence.

Treat uncertainty as useful output

The best extraction system is not the one that produces a number for every page. It is the one that can say whether a value is a current variant price, a range, a conditional promotion, an aggregate, or unresolved display text—and show why.

Keep presentation and normalized data side by side. Prefer explicit offer fields over text parsing. Track selected variants and capture time. When sources disagree, preserve the conflict. Those choices turn a brittle scraper into a dependable price-and-availability data pipeline.

Ready to build an evidence-first extraction workflow? Sign up for PagePith.

Sources

  1. Offer - Schema.org TypeSchema.org
  2. AggregateOffer - Schema.org TypeSchema.org
  3. How To Add Merchant Listing Structured DataGoogle Search Central
  4. Product Variant Structured DataGoogle Search Central
  5. How To Add Product Snippet Structured DataGoogle Search Central
  6. Intro to How Structured Data Markup WorksGoogle Search Central
  7. JSON-LD 1.1JSON-LD Community Group
  8. HTML Standard: Custom Data AttributesWHATWG
Extract Website Prices and Availability · PagePith