# E-commerce Scraper & Price Tracker - Products from Any Store (`tidytools/product-price-tracker`) Actor

Product name, price, sale price, stock, SKU, GTIN, brand, rating and variants from any online store's product pages, category pages or sitemap. Schedule it to track price drops and back-in-stock. $2 per 1,000 products, $1 per 1,000 unchanged checks.

- **URL**: https://apify.com/tidytools/product-price-tracker.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** E-commerce, Automation, Marketing
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### What does E-commerce Scraper & Price Tracker do?

**Paste product, category or store URLs, get one row per product (name, price, sale price, currency, stock, SKU, GTIN, brand, rating, variants), at $2 per 1,000 products.** Scheduled with price tracking on, unchanged products cost only $1 per 1,000. Pages without product data, blocked or failed pages, duplicates and invalid lines are free. No start fee.

It reads **product data from any online store**: give it product pages, category pages or just a store's home page, and it returns one clean row per product with **name, price, sale price, currency, stock, SKU, GTIN/EAN, brand, rating, image and every variant** (size, color ...) with its own price and stock.

Schedule it and it becomes a **price and stock tracker**: each run is compared with the previous one, and every product says whether its price dropped or rose (with the old price), a sale started or ended, or it came back in stock.

- 🛒 **Any store, no per-site setup**: it reads the product data stores publish for Google and link previews (schema.org JSON-LD and Microdata, Open Graph product tags) and, on Shopify stores, the public product JSON with exact variant prices and stock. Shopify, WooCommerce, Magento, BigCommerce, Salesforce Commerce Cloud, custom stores ...
- 🔗 **Category and home pages**: product links on a category or search page are followed; for a home page the store's sitemap is read to find product pages. Listing pages are free.
- 📉 **Built-in change tracking**: `trackChanges` keeps a price history per monitor; unchanged products cost a reduced check fee, so daily monitoring stays cheap.
- 🧾 **Honest and failure-free billing**: blocked pages, pages without product data, duplicates and invalid lines are free rows with an `errorType`.

#### Real results (3 October 2026)

10 inputs (product pages, a Shopify collection, a store home page and big retailers), "Render JavaScript" on, 3 products per listing page: 11 products in 92 seconds, **$0.024**.

| Store | Product | Price | Stock | Variants | GTIN | Source |
|---|---|---|---|---|---|---|
| allbirds.com | Men's Tree Runner - Jet Black (White Sole) | 100 USD | in_stock | 7 | 843416184854 | Shopify JSON + JSON-LD |
| allbirds.com (from /collections/mens) | Men's Cruiser - Medium Grey | 105 USD | in_stock | 14 | 196942184509 | Shopify JSON + JSON-LD |
| deathwishcoffee.com (from the home page's sitemap) | Dark Roast Instant Coffee | 11.99 USD | in_stock | 4 | 856413007408 | Shopify JSON + JSON-LD |
| walmart.com | Restored Apple AirPods Pro (2nd Generation) | 146.79 USD | in_stock | - | 194253397168 | JSON-LD (rating 4.6, 11,634 reviews) |
| nike.com | Nike Air Force 1 '07 Men's Shoes | 115 USD | (not published) | 24 | per variant | JSON-LD |
| ikea.com | BILLY bookcase | 249 USD | (not published) | - | - | JSON-LD (rating 4.6, 77 reviews) |
| uniqlo.com | AIRism Cotton Oversized T-Shirt | 24.90 USD | in_stock | 100 | - | JSON-LD after browser rendering |

Not charged in the same run: patagonia.com (no product data in the page, also after rendering), amazon.com (the page served to our honest crawler had no product data), bestbuy.com (no answer within the time limit).

Tracking test: the same 3 products run again with `trackChanges` and `onlyChanges`, after the saved state was edited to an older price and an out-of-stock state: Allbirds came back as `changes: ["price_drop"]`, `previousPrice: 120`, `priceChange: -20`, `priceChangePercent: -16.7`; Death Wish Coffee as `changes: ["back_in_stock"]`; the unchanged IKEA product was not returned and cost $0.001.

### Use cases

- **Competitor price monitoring**: track a competitor's best sellers daily and get only the price drops, sales and restocks.
- **MAP / reseller compliance**: check your products on reseller sites against your minimum advertised price.
- **Back-in-stock alerts**: watch sold-out products and act when `back_in_stock` appears.
- **Catalog and feed building**: name, brand, GTIN, images and variants from a list of product URLs, for comparison sites or affiliate feeds.
- **Market research**: price ranges and ratings across many stores in a category.

### How much does it cost?

| Event | Price | When |
|---|---|---|
| Product | **$2.00 / 1,000 products** ($0.002 each) | a product read with its name and price or stock; in tracking mode: a new or changed product |
| Unchanged product check | **$1.00 / 1,000** ($0.001) | tracking mode only: a product checked again with the same price and stock |
| Browser rendering | $0.002 | only with "Render JavaScript pages": added to a product that needed a real browser |

**Free:** category, search and home pages (listing pages), pages without product data, blocked or failed pages, robots.txt-disallowed pages, duplicates and invalid lines. **No start fee.** Larger Apify plans get the usual volume discount (Silver 10%, Gold and above 20%).

**Example:** tracking 1,000 products once a day costs $2.00 on the first day, then $1.00 per day plus $0.001 more for each product whose price or stock changed.

For comparison (Apify Store, 3 October 2026): apify/e-commerce-scraping-tool (789 users in the last 30 days) charges $0.006 per product, $0.0005 per listing page and $0.003 per product over residential proxies; price trackers for single stores (truefetch Amazon / Walmart) charge about $0.009 per result plus $0.01 per start. This Actor does not use proxies or browsers by default and does not solve bot checks, so stores that block crawlers (often the largest marketplaces) return free `blocked` or `no_product_data` rows instead.

#### Control your cost

- `maxProducts` caps the products per run (and so the cost); `maxProductsPerInput` caps how many product links are followed from one category or home page.
- The run logs its worst-case plan at the start and compares it with your **maximum charge per run**; when the next product could pass it, no new page is started (`SUMMARY.status: LIMIT_REACHED`, `SUMMARY.notProcessed`).
- Every row has `charged: true` or `false`; failed rows say "(not charged)". If Apify restarts the run, pages already done are skipped and not charged again.

### How to use it

1. **URLs**: product pages, category or search pages, or store home pages, one per line (several in one line can be separated by spaces, commas or semicolons; pasted quotes, `<>` and brackets are removed). The same product page in several spellings (`www.` or not, a trailing slash, `utm_` tracking parameters, `#reviews`, a Shopify `/collections/x/products/y` path) is read and charged once.
2. Optional: **Track price and stock changes** with a **Monitor name** (one per product list), and **Output only changed products**. Schedule the task (Apify Console → Schedules) daily or hourly.
3. Optional: **Render JavaScript pages** for stores that build product pages in the browser (e.g. Uniqlo).
4. Click **Start**. **Products (overview)** shows the key columns, **Price and stock changes** the tracking view, **Products (all fields)** everything including variants.

#### Input example

```json
{
    "urls": [
        "https://www.allbirds.com/products/mens-tree-runners",
        "https://www.allbirds.com/collections/mens",
        "https://www.walmart.com/ip/Apple-AirPods-Pro-2nd-Generation/1752657021"
    ],
    "maxProductsPerInput": 20,
    "trackChanges": true,
    "monitorName": "competitor-shoes",
    "onlyChanges": false
}
```

#### Output example

Real result (3 October 2026), shortened to 2 of 7 variants and without the store's 19 tags:

```json
{
    "input": "https://www.allbirds.com/products/mens-tree-runners",
    "url": "https://www.allbirds.com/products/mens-tree-runners",
    "finalUrl": "https://www.allbirds.com/products/mens-tree-runners",
    "success": true,
    "store": "allbirds.com",
    "platform": "Shopify",
    "name": "Men's Tree Runner - Jet Black (White Sole)",
    "brand": "Allbirds",
    "sku": "TR3MJBW080",
    "gtin": "843416184854",
    "price": 100,
    "currency": "USD",
    "listPrice": null,
    "onSale": false,
    "discountPercent": null,
    "priceMin": 100,
    "priceMax": 100,
    "availability": "in_stock",
    "inStock": true,
    "condition": "new",
    "category": "Shoes",
    "image": "https://cdn.shopify.com/s/files/1/1104/4168/files/TR3MJBW080_SHOE_LEFT_GLOBAL_MENS_TREE_RUNNER_JET_BLACK_WHITE.png?v=1751165486",
    "variantCount": 7,
    "variantsInStock": 1,
    "variants": [
        { "id": "33179624669264", "name": "8", "sku": "TR3MJBW080", "gtin": "843416184854", "price": 100, "listPrice": null, "currency": "USD", "availability": "in_stock", "options": { "size": "8" } },
        { "id": "33179624702032", "name": "9", "sku": "TR3MJBW090", "gtin": "843416184861", "price": 100, "listPrice": null, "currency": "USD", "availability": "out_of_stock", "options": { "size": "9" } }
    ],
    "dataSource": "shopify-json+json-ld",
    "via": "direct",
    "charged": true,
    "checkedAt": "2026-10-03T14:47:14.018Z"
}
```

#### What the fields mean

- **price / listPrice / onSale / discountPercent**: the current price; `listPrice` is the "was" price when the store publishes one (a strikethrough price in JSON-LD or Shopify's compare-at price).
- **availability**: `in_stock`, `out_of_stock`, `preorder`, `backorder`, `limited`, `in_store_only`, `discontinued`, or `null` when the store does not publish it. `inStock` is the yes/no version.
- **variants**: up to 100 per product, each with its own SKU, GTIN, price, stock and options.
- **dataSource**: where the data came from (`json-ld`, `microdata`, `opengraph`, `shopify-json`, or a combination). **via**: `direct` (Apify's network), `backend` (our servers), `browser` (rendered).
- **change / changes** (tracking): `new`, `changed`, `unchanged`, or `removed` (a tracked product that now returns 404, free); `changes` lists `price_drop`, `price_increase`, `sale_started`, `sale_ended`, `back_in_stock`, `out_of_stock`, `availability`, `variants_in_stock`.
- **SUMMARY** (key-value store): products, failures by `errorType`, listing pages and product links found, data sources, platforms and tracking counts.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/tidytools~product-price-tracker/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://www.allbirds.com/collections/mens"],"maxProductsPerInput":50}'
```

**With AI agents (MCP):** connect Apify's MCP server (`https://mcp.apify.com?tools=tidytools/product-price-tracker`) to Claude, Cursor or any MCP client and ask, for example, "What do the 20 products on this category page cost, and which are out of stock?". Minimal input: `{"urls": ["https://shop.example.com/products/x"]}`. Failed items are not charged and carry an `errorType`.

### Limitations

- **Only data the store publishes**: products are read from the structured data, product tags and public product JSON in the page. A store that publishes none of these (or only after JavaScript runs, without "Render JavaScript") returns `no_product_data` (free). Some stores do not publish stock (`availability: null`).
- **No bot-check solving**: requests use our honest TidyTools user agent and respect robots.txt. Stores that block crawlers (for example some large marketplaces and retailers behind Akamai or PerimeterX) return free `blocked`, `timeout` or `no_product_data` rows. A proxy (Apify proxy, billed to your account) can be added for Apify's network.
- **Listing pages**: product links are recognized by their URL pattern, the page's structured data and the product-link markup of common themes (Magento, WooCommerce ...); infinite-scroll category pages show only the products in their first HTML. Prices are shown as the store serves them to a visitor without a login, location or cookie (some stores show other prices or currencies per country).
- **Tracking** compares price, currency, list price, stock and variants in stock; title and image changes are not reported.

### FAQ

**Does the Actor collect usage data?** The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.

**Does it work on Amazon?** Usually not: Amazon serves our honest crawler pages without product data, so those rows come back as free `no_product_data` or `blocked` rows. Most Shopify, WooCommerce, Magento and BigCommerce stores and many big retailers (Walmart, Nike, IKEA, Uniqlo) work.

**How do I get alerts for price drops?** Turn on **Track price and stock changes** (`trackChanges`), keep the same **Monitor name**, turn on **Output only changed products** (`onlyChanges`) and schedule the Actor (for example daily): each run then returns only products whose price or stock changed. Connect the run to e-mail, Slack, Zapier, Make or n8n through an Apify integration.

**Why is the price different from what I see in my browser?** The page is read like a first-time visitor without a login, cookie or location. Stores that show other prices or currencies per country can differ; use [Regional Price Checker](https://apify.com/tidytools/regional-price-check) to compare regions.

**What happens if I enter nothing or a wrong URL?** The run ends normally (it does not fail) with a free row per bad line, `errorType: "invalid_input"` and a message saying what to fix. Nothing is charged. This includes numbers or objects in the URL list and private or local addresses (`localhost`, `192.168.x.x`). Per product page the other free outcomes are `not_found` (HTTP 404), `dns_error`, `blocked`, `timeout`, `http_error`, `robots_disallowed` and `no_product_data`.

**Can I send options as text from the API?** Yes: yes/no options accept `true`/`false`, `"true"`/`"false"`, `"yes"`/`"no"` and `1`/`0`; numbers accept `"20"` or `"1,000"`. A value that cannot be read uses the default and is listed in `SUMMARY.inputNotes`; a number outside the allowed range is set to the nearest limit (also noted).

**A product appears on two of my category pages. Is it charged twice?** No. Each product page is read once per run, also when two category pages link to it or it is also in your URL list; the second category page still counts as a listing page (free).

**Do Shopify collection links (`/collections/…/products/…`) return variants?** Yes. When a collection link of a Shopify store has the product but no variants, the Actor also reads the same store's `/products/<handle>.js`, and if that has no usable variants, `/products/<handle>` once more. It checks robots.txt before each read and follows same-site redirects only. Everything stays in one row: `input` and `url` keep your link, `finalUrl` may be `/products/<handle>`, `dataSource` includes `+shopify-json`, and `variantCount` and `variants` list each variant's price and stock. You are still charged once per product; if the extra read fails or is blocked, the original page's product and price are kept. When Shopify Markets shows a local currency that differs from the store data, the page's price and currency are kept and the variant prices are left empty, so two currencies are never mixed.

**Do I need "Render JavaScript pages"?** Only for stores that add their product data with JavaScript. Leave it off first: it is cheaper. A free `no_product_data` row means no product data was found in the page; rendering may help if the store adds that data with JavaScript, but some pages have none at all.

### Related Actors

- [Shopify Store Products Scraper](https://apify.com/tidytools/shopify-store-products-scraper): a whole Shopify catalog in a few requests.
- [Regional Price Checker](https://apify.com/tidytools/regional-price-check): the same page's prices from the US, Singapore, London and Taiwan.
- [Website Change Monitor](https://apify.com/tidytools/website-change-monitor): any text or element on a page.

# Actor input Schema

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

One per line: product pages from any online store, category or search result pages (their product links are followed), or a store's home page (product pages are found in its sitemap). Bare domains work too; pasted quotes and brackets are removed, and the same product in several spellings (www, utm\_ parameters, #fragment) is read once. Invalid lines become free rows with errorType "invalid_input".

## `startUrls` (type: `array`):

Optional: URLs as request objects, or a link to a text/CSV file with one URL per line ("requestsFromUrl").

## `discoverProducts` (type: `string`):

What to do with a URL that is not a product page. Listing pages themselves are free; each product read from them is charged.

## `maxProductsPerInput` (type: `integer`):

How many product links are followed from one category, search or home page.

## `maxProducts` (type: `integer`):

The run stops after this many products (each one is charged). With the maximum charge per run, this caps the cost.

## `trackChanges` (type: `boolean`):

Compare every product with the previous run of the same monitor (schedule this Actor daily or hourly): change new / changed / unchanged, price drop or increase with the old price, sale started or ended, back in stock, out of stock. Unchanged products cost a reduced check fee.

## `monitorName` (type: `string`):

Name of the saved price history (letters, digits, dashes). Use one name per product list; empty = derived from the first URL.

## `onlyChanges` (type: `boolean`):

With tracking on: new and changed products are returned; unchanged products are counted in the run summary instead of being returned (they still cost the reduced check fee).

## `alertIfPriceBelow` (type: `number`):

Optional, needs "Track price and stock changes": tracked product rows get "conditionMatched": true while the price is at or below this number (in the product's own currency, also on the first run), and "conditionReasons" when they are not. Several alert conditions set together must all hold. A wrong type is rejected before the run starts; a number below 0 ends the run with a free "invalid_input" row instead of a wrong threshold.

## `minDropPercent` (type: `number`):

Optional, needs "Track price and stock changes": tracked rows get "conditionMatched": true when the price fell by at least this percentage since the previous run (100 to 90 matches 10; 100 to 95 does not). A wrong type is rejected before the run starts; a number of 0 or less, or above 100, ends the run with a free "invalid_input" row instead of a wrong threshold.

## `restockOnly` (type: `boolean`):

Optional, needs "Track price and stock changes": rows are "conditionMatched": true only when the product was not buyable before and is buyable again ("back_in_stock"). Combine it with the price conditions to be alerted about restocks at the right price.

## `includeVariants` (type: `boolean`):

List each variant (size, color...) with its own SKU, GTIN, price and stock when the store publishes them (up to 100 per product).

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

Add the product description as plain text (up to 5,000 characters).

## `renderJavaScript` (type: `boolean`):

When a page has no product data in its HTML, load it once more in a real browser (Cloudflare Browser Rendering). Adds the "browser-render" fee only when the rendered page gave a product.

## `httpVia` (type: `string`):

Some stores block requests from data centers. Auto retries from our servers in other regions; the identity stays our honest TidyTools user agent and robots.txt is respected on every route.

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

Only used for requests sent from Apify's network. Apify proxy usage is billed to your Apify account.

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

How many pages are read at the same time.

## `pageTimeoutSecs` (type: `integer`):

A page that takes longer is reported as a timeout (free).

## Actor input object example

```json
{
  "urls": [
    "https://www.allbirds.com/products/mens-tree-runners",
    "https://www.deathwishcoffee.com/products/death-wish-instant-coffee-1",
    "https://www.ikea.com/us/en/p/billy-bookcase-white-00263850/"
  ],
  "discoverProducts": "auto",
  "maxProductsPerInput": 10,
  "maxProducts": 50,
  "trackChanges": false,
  "monitorName": "",
  "onlyChanges": false,
  "restockOnly": false,
  "includeVariants": true,
  "includeDescription": false,
  "renderJavaScript": false,
  "httpVia": "auto",
  "maxConcurrency": 8,
  "pageTimeoutSecs": 60
}
```

# Actor output Schema

## `products` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `full` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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",
        "https://www.deathwishcoffee.com/products/death-wish-instant-coffee-1",
        "https://www.ikea.com/us/en/p/billy-bookcase-white-00263850/"
    ],
    "maxProductsPerInput": 10,
    "maxProducts": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/product-price-tracker").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",
        "https://www.deathwishcoffee.com/products/death-wish-instant-coffee-1",
        "https://www.ikea.com/us/en/p/billy-bookcase-white-00263850/",
    ],
    "maxProductsPerInput": 10,
    "maxProducts": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("tidytools/product-price-tracker").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",
    "https://www.deathwishcoffee.com/products/death-wish-instant-coffee-1",
    "https://www.ikea.com/us/en/p/billy-bookcase-white-00263850/"
  ],
  "maxProductsPerInput": 10,
  "maxProducts": 50
}' |
apify call tidytools/product-price-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidytools/product-price-tracker"
        }
    }
}
```

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/beBAgbCSEHDaC5Aor/builds/48wJRkfGwuNNc1VJM/openapi.json
