Skip to content
Scraping API · Use case

Scrape product prices and stock status as typed JSON

A scrape product prices API has one job: give you a number you can compare, not a string with a currency symbol glued to it. Price monitoring, competitor tracking, marketplace feeds and affiliate catalogs all need the current price and whether the item is in stock, from stores that never agreed on a markup. The Scraping API reads both with a few typed rules per store.

The problem

Every store formats the same price differently

One shop prints £51.77, another 51,77 € with a strikethrough list price next to it, a third renders the price after the page loads. The value you want is in there, but it arrives as text in a different element on every domain, and the stock status is a sentence rather than a flag.

A regular expression per store works until the template changes, and parseFloat on the wrong element silently records the old price or the shipping fee. Stores that render prices in the browser return nothing to a plain HTTP fetch, and larger retailers put antibot protection in front of the product pages you care about most.

Extraction rules make the price a typed field. type: 'number' returns a number, an array of rules tries structured markup first and the visible label second, and waitForSelector holds the extraction until a client-rendered price exists. The type reference lists every validator.

How it works

How to extract a price from a product URL

Start with the visible price, then harden the rule with fallbacks. Each store gets its own small rule set, and the result has the same shape for all of them.

1 · Price as a number, stock as text
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const { price, stock } = await microlink.extract(
  'https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html',
  {
    price: { selector: '.product_main .price_color', attr: 'text', type: 'number' },
    stock: { selector: '.product_main .availability', attr: 'text' }
  }
)

On this demo store price comes back as 51.77 and stock as the label In stock (22 available). Turning that label into a boolean is one comparison on your side.

2 · Fallbacks across templates
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const rules = {
  price: [
    { selector: '[itemprop=price]', attr: 'content', type: 'number' },
    { selector: '.price-now', attr: 'text', type: 'number' },
    { selector: '.price', attr: 'text', type: 'number' }
  ],
  currency: { selector: '[itemprop=priceCurrency]', attr: 'content' }
}

const { price, currency } = await microlink.extract(url, rules)

The rules run in order and the first one that yields a valid number wins, so structured microdata is preferred and the visible label is the safety net. The currency travels as its own field.

3 · Client-rendered stores
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const { price } = await microlink.extract(
  url,
  { price: { selector: '.price', attr: 'text', type: 'number' } },
  { prerender: true, waitForSelector: '.price', ttl: '1h' }
)

prerender forces a browser, waitForSelector waits for the price element, and ttl keeps the result cached for an hour so repeated checks within that window are served from cache.

Parameters used
  • type number returns a numeric price. A value that is not a valid number becomes null.
  • selector The first element matching the price selector. Scope it to the main product block.
  • waitForSelector Waits for the price element on stores that render it in the browser.
  • prerender auto by default. Set true when the price is missing from the initial HTML.
  • ttl How long a price stays cached, from 1 minute to 31 days. Pro plans.
  • proxy.location Two-letter country code for stores that show a different price per country. Pro plans.

There is no scheduler on the Microlink side: run the calls from your own job at the interval you need and compare the numbers there. When the price already sits in Open Graph product tags and you also want the title and image, extract custom fields alongside metadata returns everything in one response.

Why it works

Why typed rules make a sturdier ecommerce price scraper

A price monitor fails quietly: the job keeps running and stores the wrong value. Types and fallbacks turn those quiet failures into nulls you can see.

01 · Comparable values
The price arrives as a number.
No currency symbols, no thousands separators to strip, no string comparison. Store the number, keep the currency as a separate field, and every store in your list produces the same shape.

Lists of products use the same rules nested under selectorAll, see scrape tables and repeated lists for category pages.

02 · Resilient
Fallback chains survive redesigns.
When a store renames its price class, the next rule in the array takes over. When every rule fails, the field is null rather than a stale or wrong number, which is the signal to update the selectors.

The data extraction troubleshooting guide covers null fields, wrong selectors and pages that were not ready yet.

03 · Reachable
Protected stores go through the proxy automatically.
On Pro plans, when a store answers with an antibot wall, the request escalates through proxy tiers up to residential and remembers what worked for that domain. On the free tier the same wall returns EPROXYNEEDED.

When not to: if the price only appears after choosing a size or color, a single rule reads the default variant. Click the option inside a remote Puppeteer function and read the price there.

FAQ

How do I scrape a product price as a number?

Write a rule with the price element as selector, attr text and type number. The value comes back as a number, or null when the element is missing or its text is not a valid price.

Can an ecommerce price scraper keep the currency?

Yes, as a separate field. Stores that use schema.org microdata expose it in an element with itemprop priceCurrency, so a second rule reading its content attribute gives you the ISO code next to the numeric price.

How do I scrape prices from stores that render with JavaScript?

Add prerender: true and waitForSelector with the price selector. The page renders in a real browser and the rules run once the price element exists. Scraping JavaScript-rendered pages covers the wait options in depth.

How often can I re-check a scraped product price?

As often as your job calls the API. Responses are cached for 24 hours by default, so on a Pro plan set ttl to your check interval, as low as one minute, or pass force: true to skip the cache. Cache hits never count against your quota.

What happens when a store blocks my price scraper?

On the free tier the API returns EPROXYNEEDED, which means the store uses antibot protection and needs a Pro plan. On Pro the proxy is on by default and resolves automatically, and proxy.location pins the country when prices vary by region. See the proxy reference.
Related use cases

Solve the next problem with the same API

Any website to JSON

Declare the fields you want as CSS selector rules and get typed JSON back, with null for anything the page does not have.

Custom fields from JavaScript apps

Render React, Vue or Angular apps in a real browser, wait for the element you need, then run your rules.

Tables and lists to JSON arrays

Turn table rows, product grids and search results into an array of objects, one nested rule per column.

Custom fields alongside metadata

Get prices, ratings, headings or any CSS selector, typed and returned next to the normalized metadata.

Screenshot a site from another country

Pin the request to a country with a two-letter code and capture the prices and copy a local visitor sees.

Link previews for bot-protected sites

Unfurl links to sites behind Cloudflare or DataDome: on a Pro key the built-in proxy resolves automatically.

Ready to track prices as numbers?

Typed prices and stock from any product URL. Start on the free tier and write the rules for your first store today.