# Back in Stock Alert Scraper: Products, Sizes, Colours

> Build back in stock alerts with a scraper: the stock fields to read, size and colour restocks from variants, null vs out of stock, check cadence, and cost.

- URL: https://datatooly.xyz/guides/back-in-stock-alert-scraper/
- Author: Omar Eldeeb (https://datatooly.xyz/about/)
- Published: 2026-10-20
- Updated: 2026-10-20
- Free tool: [E-commerce price tracker — scrape product prices, stock and variants](https://datatooly.xyz/ecommerce-price-tracker/)
- Tags: ecommerce, python, webscraping, automation

A back-in-stock alert is a scheduled check of product pages that compares each item's stock flag, and each size's or colour's, with the last check, and alerts when one flips from out of stock to in stock. Read `inStock` (true, false or unknown) per product and per variant, ignore unknowns, run hourly, and alert on the flip.

This guide builds that with [E-commerce Product Scraper Pro](https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu) on Apify, using real rows from test runs on 17-20 October 2026, including one real size restock on Zalando.

## The stock fields you get

Every product row has three stock fields, and every entry in `variants[]` has its own:

| Field | Meaning | Real values seen |
|---|---|---|
| `inStock` | Normalised flag: `true`, `false`, or `null` (unknown) | `true`, `false`, `null` |
| `availability` | The stock state as the page gives it, usually schema.org's term | `InStock`, `OutOfStock`, `PreOrder`, `BackOrder`, `LimitedAvailability`, or the site's own text |
| `stockQty` | A count read from text like "Only 3 left" | `1` on `amazon.com`'s Anker USB-C cable (`LimitedAvailability`) |
| `variants[].inStock`, `variants[].availability` | The same, per size / colour / pack, with the variant's `sku`, `price` and `options` | `{"Size": "43"}`, `{"Color": "Damson Blue"}` |

Across 10,645 product rows from our October test runs, `inStock` was `true` 8,344 times, `false` 406 times (387 `OutOfStock`, 13 `PreOrder`, 6 `BackOrder`) and `null` 1,895 times. Note that pre-order and back-order count as not in stock.

`null` matters most. On `amazon.de`, 50 rows came back with `inStock: null` and an `availability` of "Dieser Artikel kann nicht an den von dir ausgewählten Zustellungsort versendet werden" (Amazon saying it can't deliver to the selected location). That says nothing about stock. If your alert logic treats `null` as "out", the next normal page looks like a restock. **Only a known `false` followed by a known `true` is a restock.**

## Variant-level stock: sizes and colours

`includeVariants` is on by default. Here are two variants of a real John Lewis row from 2026-10-18 (trimmed); two of its five colours were out of stock while the product as a whole was in stock:

```json
{
  "name": "Sage Barista Express Impress Bean to Cup Coffee Machine",
  "inStock": true,
  "availability": "InStock",
  "variants": [
    { "sku": "241219588", "price": 599.95, "inStock": true, "availability": "InStock", "options": { "Color": "Brushed Stainless Steel" } },
    { "sku": "110092884", "price": 599.95, "inStock": false, "availability": "OutOfStock", "options": { "Color": "Damson Blue" } }
  ]
}
```

Most "back in stock" demand is like this: one colour or size, not the product. So watch the variants.

## Option 1: let monitor mode do the diff

Run the actor with `monitorMode: true` and `onlyChanges: true` on a schedule. It stores every product and variant between runs and writes only what changed:

- When the whole product flips from out to in stock, `changeType` is `back-in-stock`.
- When one variant flips, the product's `changeType` is `changed`, `changes` contains `variant-stock-changed`, and the variant appears in `changedVariants` with `previousInStock` and `inStock`.
- A stock count drifting (12 left, then 11) is not reported as a change, so limited-stock items don't alert every run.

This reads one monitor run and prints restock alerts at both levels:

```python
"""Restock alerts from one monitor-mode run (product level and size/colour level)."""
import os
import sys

from apify_client import ApifyClient


def restock_alerts(rows):
    for r in rows:
        if r.get("recordType") != "product":
            continue
        if r.get("changeType") == "back-in-stock":                   # the whole product came back
            yield f"{r['name']} is back in stock at {r['price']} {r['currency']}: {r['url']}"
        for v in r.get("changedVariants") or []:                      # one size or colour came back
            if v.get("previousInStock") is False and v.get("inStock") is True:
                yield f"{r['name']} ({v['name']}) is back in stock at {v['price']}: {r['url']}"


rows = ApifyClient(os.environ["APIFY_TOKEN"]).run(sys.argv[1]).dataset().list_items(clean=True).items
alerts = list(restock_alerts(rows))
print(f"{len(rows)} changed rows, {len(alerts)} restock alert(s)")
for a in alerts:
    print(a)
```

On the monitor run `heds8zid9ccytKKhQ` (2026-10-20) it printed `2 changed rows, 0 restock alert(s)`: both changes in that run were price moves, which is the correct answer. To get a message out of it, use the same pattern as the webhook handler in the [price drop alerts guide](https://datatooly.xyz/guides/price-drop-alerts-email-slack/), which already forwards `back-in-stock` rows to Slack.

## Option 2: diff two runs yourself

If you already scrape without monitor mode, compare two runs' datasets. This keys every product and every variant by product URL plus SKU:

```python
"""Back-in-stock alerts: product level and size/colour level. pip install apify-client"""
import os
import sys

from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])


def products(run_id):
    items = client.run(run_id).dataset().list_items(clean=True).items
    return [r for r in items if r.get("recordType") == "product"]


def stock_map(rows):
    """(product URL, variant SKU or '') -> (inStock, label, price). inStock None = unknown, never 'out'."""
    out = {}
    for r in rows:
        out[(r["url"], "")] = (r.get("inStock"), r["name"], r.get("price"))
        for v in r.get("variants") or []:
            if v.get("sku"):
                label = " / ".join(f"{k} {x}" for k, x in (v.get("options") or {}).items()) or v.get("name")
                out[(r["url"], v["sku"])] = (v.get("inStock"), f"{r['name']} ({label})", v.get("price"))
    return out


def restocks(old_rows, new_rows):
    before, after = stock_map(old_rows), stock_map(new_rows)
    for key, (now, label, price) in after.items():
        was = before.get(key, (None,))[0]
        if was is False and now is True:              # only a known "out" -> known "in" counts
            yield label, price, key[0]


if __name__ == "__main__":
    old_run, new_run = sys.argv[1], sys.argv[2]
    for label, price, url in restocks(products(old_run), products(new_run)):
        print(f"BACK IN STOCK: {label} at {price} -> {url}")
```

Run against two of our test runs on 2026-10-17, one at 13:58 UTC (`pm3sEpCiKnUa6DuAO`) and one at 14:12 UTC (`LP3JVIcrH0yubpdPR`), it found a real size restock:

```text
BACK IN STOCK: MASTERS COURT SUEDE PANELED SNEAKER - Sneaker low - grey fog (Size 43) at 96 -> https://www.zalando.de/polo-ralph-lauren-masters-lace-unisex-sneaker-low-grey-fog-po215o037-c11.html
```

Later runs that day show how brief it was: size 43 was still in stock at 14:12 UTC and out of stock again by 19:15 UTC. Restocks can be short, which is the whole case for automated alerts.

## Avoid false alerts

Three things produced misleading flips in our test data:

1. **Unknown read as out.** Handled above: never treat `null` as `false`.
2. **Different pages disagree about the same variant.** On Walmart, a cable's 5-pack variant was `OutOfStock` on its own page but listed as `InStock` in the variant picker of its 1-pack and 2-pack pages, in the same run, consistently on 2026-10-17. Trust the variant's own page for variants you care about, key your state by the exact URL you watch (as both scripts do), and don't merge readings from different pages.
3. **Very short restocks.** Re-check once before acting on an alert, or require two consecutive "in" readings if a false alarm is costly for you (for example, if the alert triggers an automatic purchase).

## How often to check, and what it costs

Each run pays a start fee, so cadence drives cost as much as the number of products. With `onlyChanges`, an unchanged product is billed as "product checked" and a changed one as "product scraped". From the README's pricing table (pay per event):

| | FREE | GOLD |
|---|---|---|
| Product checked (unchanged) | $0.0012 | $0.0002 |
| Product scraped (changed or new) | $0.0048 | $0.0008 |
| Actor start, per run | $0.005 | $0.005 |

For 50 product URLs on datacenter-reached sites, with nothing changing:

- **Hourly** (24 runs): 1,200 checks. FREE: 1,200 × $0.0012 + 24 × $0.005 = **$1.56/day**. GOLD: 1,200 × $0.0002 + 24 × $0.005 = **$0.36/day**.
- **Every 30 minutes** (48 runs): FREE **$3.12/day**, GOLD **$0.72/day**. At GOLD, a third of that is start fees.

So group products into one watchlist per cadence rather than one task per product, and keep the fast cadence for the few items that sell out. Sites reached through residential proxies or a browser (Noon, IKEA, Bloomingdale's and others in the README's table) also bill traffic per MB or a browser-page fee on each check, because the page is fetched even when nothing changed. Apify's free plan gives $5 of usage a month with no credit card; the actor has no in-actor free trial.

Cron for the two cadences in an [Apify schedule](https://docs.apify.com/platform/schedules?fpr=v77kxu): `0 * * * *` (hourly) and `*/30 * * * *` (48 runs a day).

## During Black Friday and the holiday season

Doorbusters and limited bundles sell out and come back in waves during Black Friday week, and gift sizes and colours go out of stock in December. For that window, add a temporary second schedule to the same task, for example `*/15 * 26-30 11 *`: every 15 minutes from Thanksgiving (Thursday 26 November 2026) to Cyber Monday (30 November), 480 runs in `America/New_York`. Use it only for a short list: 20 unchanged products cost 20 × $0.0002 + $0.005 = $0.009 per run on GOLD, or $4.32 for the 480 runs ($13.92 on FREE). Delete it afterwards. The [holiday sales calendar](https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/) lists each event's window, and the [Christmas price tracker](https://datatooly.xyz/guides/christmas-price-tracker/) covers the December gift season.

## Limits

- Unsupported sites (eBay, Home Depot, Lowe's, Costco, Etsy, Best Buy and the others in the README matrix) can't be checked.
- Stock is what the page states. Many stores show "in stock" without a count, so `stockQty` is often `null`.
- Some sites show stock for a selected store or postcode only; the actor reads the page as it's served to its connection, so check that the market (`proxyCountry`) matches the shoppers you care about.

Related: [scraping product variants](https://datatooly.xyz/guides/scrape-product-variants-sizes-colors/), [competitor price monitoring](https://datatooly.xyz/guides/competitor-price-monitoring-scraper/), and the [e-commerce price tracker query builder](https://datatooly.xyz/ecommerce-price-tracker/).

*Disclosure: I built the actor and this site. The diff logic (known out to known in, per variant) works with any scraper's output.*

Sources: [E-commerce Product Scraper Pro README](https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu) (output fields, monitor mode, pricing, supported sites), [Apify docs: Schedules](https://docs.apify.com/platform/schedules?fpr=v77kxu), [Apify pricing](https://apify.com/pricing?fpr=v77kxu), and actor runs on 17-20 October 2026.

## Quick answers

### How do I get notified when an item is back in stock?

Use the store's own "notify me" button if it has one. For items it doesn't cover, or many items across stores, schedule a scraper to re-check the product pages, compare each product's and each variant's stock flag with the previous check, and send a message when one goes from out of stock to in stock.

### How often should a back-in-stock checker run?

Hourly is a sensible default. Every 15-30 minutes is only worth it for scarce items that sell out quickly, and only during the window when restocks happen, because every run has a start cost on top of the per-product checks.

### Can I track a specific size or colour, not just the product?

Yes, if the scraper returns variants. Each variant has its own SKU and stock flag, so you can alert on 'size 43 is back' even when the product as a whole never went out of stock.

### Why does a scraper say the stock is unknown?

Some pages don't state stock in a readable way, or show a message such as 'cannot be delivered to your location' instead. Treat unknown as unknown, not as out of stock, or you'll send false back-in-stock alerts when the page starts showing stock again.

## Related guides

- [Retail Sales Calendar 2026-2027: When to Track Prices](https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/)
- [Amazon Price History Without Keepa: Build Your Own](https://datatooly.xyz/guides/amazon-price-history-without-keepa/)
- [Amazon Price Scraper: Prices by ASIN on Any Marketplace](https://datatooly.xyz/guides/amazon-price-scraper-asin/)
- [Back to School Price Tracker: Tax Holidays & Laptops](https://datatooly.xyz/guides/back-to-school-price-tracker/)
- [Compare Black Friday Prices Across Stores (Google Shopping)](https://datatooly.xyz/guides/black-friday-google-shopping-price-comparison/)
- [Black Friday Price Tracker: Watch 2026 Prices Daily](https://datatooly.xyz/guides/black-friday-price-tracker/)
- [Boxing Day Sales Tracker UK 2026: Prices in GBP](https://datatooly.xyz/guides/boxing-day-sales-price-tracker-uk/)
- [Christmas Price Tracker: When to Buy Gifts Cheapest](https://datatooly.xyz/guides/christmas-price-tracker/)
- [Competitor Price Monitoring: Scraper Setup and Cost](https://datatooly.xyz/guides/competitor-price-monitoring-scraper/)
- [Cyber Monday Price Tracker: Hourly Checks for Nov 30](https://datatooly.xyz/guides/cyber-monday-deal-tracker/)
- [Grocery Price Tracker: Build a Basket Index in Python](https://datatooly.xyz/guides/grocery-price-tracker-scraper/)
- [MAP Monitoring: Find Minimum Advertised Price Violations](https://datatooly.xyz/guides/map-price-monitoring/)
- [Price Drop Alerts to Slack or Email with a Scraper](https://datatooly.xyz/guides/price-drop-alerts-email-slack/)
- [Prime Day Price Tracker: ASINs, Big Deal Days, 2027](https://datatooly.xyz/guides/prime-day-price-tracker/)
- [Ramadan Sale Price Tracker 2027: Noon & Amazon UAE](https://datatooly.xyz/guides/ramadan-eid-sales-price-tracking/)
- [Scrape Product Prices with Python from JSON-LD](https://datatooly.xyz/guides/scrape-product-prices-json-ld-python/)
- [Scrape Product Reviews from Walmart, Target and Amazon](https://datatooly.xyz/guides/scrape-product-reviews-walmart-target-amazon/)
- [Scrape Product Variants: Sizes, Colors and Price per SKU](https://datatooly.xyz/guides/scrape-product-variants-sizes-colors/)
- [Fake Black Friday Deals: Check the Real Discount](https://datatooly.xyz/guides/spot-fake-black-friday-discounts/)
- [Apify E-commerce Scraping Tool Alternative: Same Input](https://datatooly.xyz/guides/switch-from-apify-ecommerce-scraping-tool/)
- [When Do Black Friday Prices Drop? The 2026 Timeline](https://datatooly.xyz/guides/thanksgiving-black-friday-price-drop-timeline/)
- [Walmart and Target Price Scraper: Search and Product Pages](https://datatooly.xyz/guides/walmart-target-price-scraper/)
- [White Friday & 11.11 Tracker: Noon and Amazon UAE Prices](https://datatooly.xyz/guides/white-friday-11-11-noon-amazon-uae-prices/)
- [Shopify products.json Pagination: 250 Limit and the 25,000 Cap](https://datatooly.xyz/guides/shopify-products-json-pagination/)
- [AI Share of Voice: Measure It in ChatGPT and Perplexity](https://datatooly.xyz/guides/ai-share-of-voice-chatgpt-perplexity/)
