# Product Data Extractor API — Price, Title, Images & Stock (`highbrow_fame/product-page-extractor-api`) Actor

Name, brand, price, currency, stock, rating, images, SKU/GTIN and variants from a product page's schema.org JSON-LD, microdata or Open Graph tags. One JSON record per URL. Product Data Extractor API.

- **URL**: https://apify.com/highbrow_fame/product-page-extractor-api.md
- **Developed by:** [yestrue](https://apify.com/highbrow_fame) (community)
- **Categories:** E-commerce, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 products

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Product Data Extractor API — Price, Title, Images & Stock

Give it product page URLs from any shop and get one clean JSON record per page: name, brand, price (with a from–to range when the page lists several offers), currency, availability, SKU, GTIN/EAN/ISBN, MPN, rating and review count, images, description, category, color, material, variants, seller and condition. The data comes from the page's own structured data — schema.org JSON-LD (Product, ProductGroup, Offer, AggregateOffer), schema.org microdata and Open Graph product tags — which is what Google Shopping and social previews read too, so it works on most Shopify, WooCommerce, Magento, BigCommerce, PrestaShop and custom stores without a scraper per shop. No browser, no guessing: a page without product markup is reported as such, for free.

**Try it in 1 minute:**

1. Press **Start** — the prefilled input reads one Allbirds product page.
2. Seconds later the **Output** tab shows the record: name, brand, price and currency, stock status, SKU, rating and review count, images, description and the size variants with their own stock status.
3. Paste your own product URLs (one per line), download JSON, CSV or Excel, or switch **Include raw JSON-LD** on to get the page's whole Product object.

Price: **$0.50 per 1,000 products** (one page = one record). Pages without product markup, missing pages, pages blocked by robots.txt or a bot check, and invalid URLs are free.

**Use it to:**

- Monitor competitor prices and stock on a schedule across many shops with one Actor, instead of one scraper per site.
- Enrich a list of product links from a spreadsheet, CRM or affiliate feed with structured product data (price, GTIN, images, rating).
- Feed an AI agent or RAG pipeline with clean product facts from any URL a user pastes.
- Check that your own catalogue pages expose correct schema.org data (price, availability, GTIN) the way Google Shopping sees it.
- Build price-comparison, deal-alert or catalogue-sync tools on top of a stable record shape.

### How it works

1. **URLs** are normalised (scheme added, fragments dropped) and deduplicated; one record per page.
2. Unless you turn it off, the site's **robots.txt** is read once per domain and a page it disallows for generic crawlers gets a free `ROBOTS_DISALLOWED` record.
3. The page is fetched over plain HTTP with a desktop Chrome (or mobile Safari) identity — directly first, through the Apify proxy only to retry after a network or server error (or from the first request when you switch that on). A shop's rate limit (HTTP 429) is waited out and retried on the same route. Nothing else is requested: no scripts run, no product API is called.
4. The product is read from the page in this order of trust, later sources only filling gaps: **JSON-LD** `Product` / `ProductGroup` (also inside `@graph`, arrays and a `WebPage`'s `mainEntity`; `Offer`, `AggregateOffer`, offer arrays, `priceSpecification`, `hasVariant` variants) → **microdata** (`itemtype` schema.org/Product with `itemprop` name, price, priceCurrency, availability, sku, brand, image, ratingValue, reviewCount …, or a page-level `<meta itemprop="price">`) → **Open Graph / Twitter** tags (`og:title`, `og:image`, `product:price:amount`, `product:price:currency`, `product:availability`, `product:brand`, `product:retailer_item_id`, `product:condition`, `twitter:label1` "Price"). The `source` field says which one(s) the record came from.
5. Prices are parsed in both conventions ("1,299.00" and "1.299,00"), currency symbols become ISO codes when unambiguous (`priceCurrency` always wins), availability URLs become plain words (`InStock`, `OutOfStock`, `PreOrder`, `BackOrder`, `Discontinued`, `LimitedAvailability`, `SoldOut`, `OnlineOnly`, `InStoreOnly`), image URLs are made absolute and deduplicated, and the description becomes plain text with phone numbers and e-mail addresses removed.
6. A page with no product markup at all — or markup that names neither a product nor a price — gets a free `NO_STRUCTURED_DATA` record; a price is never guessed from the page text.

### Input

| Field | What it does |
|---|---|
| `urls` | Product pages to read, one URL per line. |
| `includeDescription` | Plain-text description, up to 5,000 characters (default on). |
| `includeRawJsonLd` | Add the page's schema.org Product object as found, in `rawJsonLd` (default off). |
| `respectRobots` | Skip pages the site's robots.txt disallows (default on). |
| `userAgent` | `desktop` (Chrome on Windows, default) or `mobile` (Safari on iPhone). |
| `maxConcurrency` | Pages read in parallel (default 5, up to 20). |
| `proxyFromFirstRequest` | Send every request through the proxy from the start (default off) — for shops that answer datacenter IPs with HTTP 429. |
| `proxyConfiguration` | Used only to retry after a network or server error unless the switch above is on; the first try is direct. A bot-check page is never retried on another IP. |

### Output

One record per URL:

| Field | What it is |
|---|---|
| `input`, `url` | The URL as given, and the final URL after redirects |
| `name`, `brand`, `sku`, `gtin`, `mpn` | Identity: name, brand, the shop's code, GTIN-14/13/12/8/EAN/UPC/ISBN, manufacturer part number |
| `price`, `priceMin`, `priceMax`, `currency` | Price as a number (the lowest when several offers are listed), the range when there is one, ISO 4217 currency |
| `availability`, `availabilityRaw` | Normalised stock status and the value as the page wrote it |
| `rating`, `reviewCount`, `bestRating` | Average rating, number of reviews/ratings, the scale's maximum when given |
| `images[]`, `image` | Absolute image URLs (deduplicated, up to 20) and the first one |
| `description`, `category`, `color`, `material`, `size` | What the page states, as plain text |
| `variants[]` | `{ name, sku, price, currency, availability, url }` from a ProductGroup's variants or several offers, up to 50 |
| `seller`, `condition` | Seller (business) name; `New`, `Used`, `Refurbished` or `Damaged` |
| `source`, `siteName`, `httpStatus` | `json-ld`, `microdata`, `opengraph` or `mixed`; `og:site_name`; the HTTP status |
| `rawJsonLd` | The schema.org Product object as found (with Include raw JSON-LD on), else null |
| `status`, `error`, `scrapedAt` | `OK` / `NO_STRUCTURED_DATA` / `NOT_FOUND` / `ROBOTS_DISALLOWED` / `INVALID_INPUT` / `FAILED`, the reason, and when |

#### Example

```json
{
  "input": "https://shop.acme.example/p/trail-runner-x1",
  "url": "https://shop.acme.example/p/trail-runner-x1",
  "name": "Trail Runner X1",
  "brand": "Acme",
  "price": 129.9,
  "priceMin": null,
  "priceMax": null,
  "currency": "EUR",
  "availability": "InStock",
  "availabilityRaw": "http://schema.org/InStock",
  "sku": "X1-BLK-42",
  "gtin": "4006381333931",
  "mpn": "ACM-X1-2026",
  "rating": 4.6,
  "reviewCount": 128,
  "bestRating": 5,
  "images": [
    "https://cdn.acme.example/img/x1-front.jpg",
    "https://shop.acme.example/img/x1-side.jpg",
    "https://cdn.acme.example/img/x1-top.jpg"
  ],
  "image": "https://cdn.acme.example/img/x1-front.jpg",
  "description": "Light trail shoe with a Vibram sole.",
  "category": "Shoes > Trail running",
  "color": "Black",
  "material": "Recycled polyester",
  "size": "42",
  "variants": [
    { "name": "Trail Runner X1 - 42", "sku": "X1-BLK-42", "price": 129.9, "currency": "EUR", "availability": "InStock", "url": "https://shop.acme.example/p/trail-runner-x1?size=42" },
    { "name": "Trail Runner X1 - 43", "sku": "X1-BLK-43", "price": 129.9, "currency": "EUR", "availability": "OutOfStock", "url": "https://shop.acme.example/p/trail-runner-x1?size=43" }
  ],
  "seller": "Acme Outdoor GmbH",
  "condition": "New",
  "source": "json-ld",
  "siteName": "Acme Outdoor Shop",
  "rawJsonLd": null,
  "httpStatus": 200,
  "status": "OK",
  "error": null,
  "scrapedAt": "2026-10-19T12:00:00.000Z"
}
```

### FAQ

**Is this an official API?** No, it's an unofficial API built on public pages — it reads the schema.org and Open Graph markup that shops publish on their own product pages for search engines and social networks. There is no shop API behind it and no account is needed.

**Why did a page come back as NO_STRUCTURED_DATA?** The page has no JSON-LD Product, no microdata Product and no Open Graph product tags in its HTML — or it fills them in with JavaScript after loading, which this version does not run. The Actor never guesses a price from the page text. Open the page source and search for `application/ld+json` or `product:price:amount` to see what the shop exposes.

**Does it work on Amazon, Walmart or other big marketplaces?** Usually not: they answer automated requests with a bot-check page, which is reported as a free `FAILED` record ("bot check") and never retried on another IP. The Actor is built for the long tail of brand and retailer stores. Some Shopify stores throttle datacenter IPs with HTTP 429 "Too many requests"; the run waits and retries, and if that keeps failing, switch **Use the proxy from the first request** on with a residential proxy group. Wherever a site offers an official product API, use that.

**Which price is `price` when the page lists several?** The lowest offer, with `priceMin`/`priceMax` giving the range and `variants[]` the individual offers. A single offer has `priceMin`/`priceMax` null. `currency` comes from `priceCurrency` when stated, else from an unambiguous symbol (€, £, $, ¥, zł, Kč …); "kr" alone is left null because it could be SEK, NOK or DKK.

**How are JavaScript-only shops handled?** They are not, in this version: only what the server sends is read. A browser-rendering option is planned; until then, such pages are free `NO_STRUCTURED_DATA` records.

**Like it?** A short review on the Store page helps other people find this Actor. Something missing or broken? Tell us on the Issues tab — we read every one.

### Related Actors

- [Web Fetch API](https://apify.com/highbrow_fame/web-fetch-api) — any URL as HTML, text, Markdown or JSON
- [Website Tech Stack Detector](https://apify.com/highbrow_fame/website-tech-stack) — CMS, frameworks and analytics of any site
- [Sitemap Extractor](https://apify.com/highbrow_fame/sitemap-url-extractor) — every URL, date and image from a site's sitemaps
- [Website Screenshot API](https://apify.com/highbrow_fame/website-screenshot-api) — full page and mobile screenshots
- [Article Extractor API](https://apify.com/highbrow_fame/article-extractor-api) — title, author, date and full text of any article

# Actor input Schema

## `urls` (type: `array`):

Product pages to read, one URL per line. Works on any shop whose page carries schema.org JSON-LD, microdata or Open Graph product tags — most Shopify, WooCommerce, Magento, BigCommerce and PrestaShop stores do. One record per URL.

## `includeDescription` (type: `boolean`):

Add the product description as plain text (HTML removed, up to 5,000 characters, phone numbers and e-mail addresses removed).

## `includeRawJsonLd` (type: `boolean`):

Add the page's schema.org Product object as it was found (field rawJsonLd), for fields this Actor does not map.

## `respectRobots` (type: `boolean`):

Skip pages the site's robots.txt disallows for generic crawlers (a free ROBOTS_DISALLOWED record). Turn off only when you are sure you may read the page.

## `userAgent` (type: `string`):

Which browser the request looks like. Desktop Chrome by default; some shops serve lighter pages with complete structured data to mobile Safari.

## `maxConcurrency` (type: `integer`):

How many pages are read at the same time. Lower it for a single shop with a strict rate limit.

## `proxyFromFirstRequest` (type: `boolean`):

Off by default: the first try of every page goes out directly and the proxy is used only for a retry. Turn on for shops that answer datacenter IPs with HTTP 429 "Too many requests" (some Shopify stores do), ideally with a residential proxy group below.

## `proxyConfiguration` (type: `object`):

The first try of every page goes out directly; the proxy is used only to try again after a network error or a server error. A bot-check page (Cloudflare, Akamai …) is never retried on another IP.

## Actor input object example

```json
{
  "urls": [
    "https://www.allbirds.com/products/mens-tree-runners"
  ],
  "includeDescription": true,
  "includeRawJsonLd": false,
  "respectRobots": true,
  "userAgent": "desktop",
  "maxConcurrency": 5,
  "proxyFromFirstRequest": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Every result as a JSON record, with a status for each input.

## `resultsCsv` (type: `string`):

The same records as CSV, for a spreadsheet.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.allbirds.com/products/mens-tree-runners"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("highbrow_fame/product-page-extractor-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": ["https://www.allbirds.com/products/mens-tree-runners"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("highbrow_fame/product-page-extractor-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.allbirds.com/products/mens-tree-runners"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call highbrow_fame/product-page-extractor-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,highbrow_fame/product-page-extractor-api"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/RgfPGgx5Em6bdrT9r/builds/vEXrwNP9EFVCLXyHs/openapi.json
