Detect Price & Stock Changes | Dataset Diff
Pricing
Pay per usage
Detect Price & Stock Changes | Dataset Diff
Spot price drops, stock changes, and catalog updates with Dataset Diff. Compare two JSON arrays or Apify datasets for field-level changes, before/after values, and numeric deltas. No scraping.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
panda studio
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Spot price drops, stock changes, and catalog updates — compare two dataset snapshots and get exactly what changed as clean, field-level JSON. Point it at yesterday's scrape and today's scrape and it tells you which records were added, modified (with before / after values and numeric deltas), missing, and removed. Ideal for recurring e-commerce price monitoring and change detection — and it processes only user-supplied data, with no scraping.
Built for recurring monitoring: e-commerce price & availability tracking, catalog audits, lead-list change detection, and any workflow where you run a scraper on a schedule and only care about what moved.
- No scraping, no browser, no anti-bot games. It only processes data you supply — inline JSON, or two Apify datasets your own token can read. Zero third-party Terms-of-Service risk.
- Dependency-free & fast. Pure Python standard library. Typical runs finish in seconds.
- Truthful by default. It won't call a record "removed" from a half-finished scrape — see Missing vs. removed below.
What you get
Every run writes one summary row plus one change row per affected record:
// summary row{ "recordType": "summary", "oldCount": 1200, "newCount": 1198,"added": 5, "modified": 41, "missing": 0, "removed": 7,"unchanged": 1152, "evaluatedKeys": 1205, "errors": [] }// a modified record{ "recordType": "change", "changeType": "modified", "key": "sku-1","changes": [{ "field": "price", "before": 19.99, "after": 17.99, "numericDelta": -2.0,"beforePresent": true, "afterPresent": true }] }// an added record (full record included){ "recordType": "change", "changeType": "added", "key": "sku-4","record": { "id": "sku-4", "title": "Mechanical Keyboard", "price": 79.0 } }
Inputs
| Field | Type | Default | What it does |
|---|---|---|---|
oldDatasetId | string | — | Apify dataset ID of the previous snapshot. Your token must read it. |
newDatasetId | string | — | Apify dataset ID of the current snapshot. |
oldItems | array | — | Previous snapshot as inline JSON (used when oldDatasetId is empty). |
newItems | array | — | Current snapshot as inline JSON. |
keyFields | array | ["id"] | Field(s) that identify a record across snapshots. Use several for a composite key (e.g. sku + variant). |
compareFields | array | [] | Only check these fields. Empty = compare all fields except keys and ignored fields. |
ignoreFields | array | ["scrapedAt","fetchedAt","crawledAt","#debug"] | Volatile fields to skip so they don't create noise. |
numericFields | array | ["price"] | Fields compared as numbers; a numericDelta is computed and the tolerance applies. Currency symbols and thousands separators are tolerated ("$1,299.00" → 1299.0). |
numericTolerance | integer | 0 | Ignore numeric changes at or below this absolute difference. |
snapshotsComplete | boolean | false | See Missing vs. removed. |
includeUnchanged | boolean | false | Also emit a row for records that didn't change. |
maxRowsPerSnapshot | integer | 50000 | Safety cap; the run errors out instead of silently truncating. |
Run it with no input and it compares a small built-in demo snapshot, so you can see the exact output shape before wiring up your own data.
Outputs
| Field | Present on | Meaning |
|---|---|---|
recordType | all | summary or change. |
changeType | change rows | added / modified / missing / removed / unchanged. |
key | change rows | The composite key of the record. |
changes | modified rows | Array of {field, before, after, numericDelta, beforePresent, afterPresent}. |
record | added / missing / removed rows | The full record. |
oldCount,newCount,added,modified,missing,removed,unchanged,evaluatedKeys | summary | Counts. |
errors | summary | Non-fatal issues (duplicate keys, missing key fields, skipped non-objects). |
Missing vs. removed — why it matters
If a record is in the old snapshot but not the new one, that can mean two very different things:
- The product was genuinely removed, or
- Your new scrape was incomplete (a timeout, a blocked page, pagination that stopped early).
Treating an incomplete scrape as mass deletions is how monitoring pipelines send false "everything is gone" alerts. So by default this Actor labels those records missing (a soft signal). Set snapshotsComplete: true only when you are confident both snapshots are complete crawls — then they are labelled removed.
Example: daily price monitoring
- Schedule your product scraper to run every morning; each run produces a dataset.
- Schedule this Actor right after, with:
oldDatasetId= yesterday's dataset IDnewDatasetId= today's dataset IDkeyFields=["sku"],numericFields=["price"],numericTolerance=0
- Wire the output to a webhook / integration. Every
modifiedrow with apricechange and a negativenumericDeltais a price drop worth acting on.
Notes
- Matching is by exact key equality; pick keys that are stable across runs (an internal
idorsku, not a position or a timestamp). - Records with duplicate keys keep the last occurrence and are reported in
errors. - The Actor reads and writes datasets only through the documented Apify REST API and never logs your input values.