Officeworks Australia Scraper Lite avatar

Officeworks Australia Scraper Lite

Pricing

from $1.00 / 1,000 results

Go to Apify Store
Officeworks Australia Scraper Lite

Officeworks Australia Scraper Lite

Scrape Officeworks (officeworks.com.au) products: name, brand, title, price, GST, was/original price, deals and clearance specials, stock by state, images, variants, specifications, ratings and reviews. Search by keyword, specials or filters, or paste product, search or category URLs.

Pricing

from $1.00 / 1,000 results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Categories

Security

Limited permissions

Share

Officeworks Scraper Lite: Products, Prices, Stock & Reviews

Officeworks Scraper Lite turns officeworks.com.au into a clean product data feed. Search by keyword with brand, price and rating filters, browse the Deals and Clearance collections, or paste product, search or category links directly. Get the current price plus any genuine was-price, savings and discount, the canonical product title, the variant matrix, availability by Australian state, and optionally the full product description, specifications, image gallery and customer reviews with an aggregate rating summary and star-rating distribution. Export to JSON, CSV or Excel, or pull the results straight into your app through the API.

Why This Scraper?

  • Two ways to find products. Search by keyword with brand, price and rating filters, or paste product, search, category or specials links directly. No need to build URLs by hand.
  • Deals and Clearance, covered natively. Browse Officeworks' own promotional collections instead of (or alongside) a keyword search, with the same filters and sort still applying.
  • Genuine reductions only. Officeworks runs an every-day-low-price model; wasPrice, savingsAmount and savingsPercent are populated only when a product is genuinely reduced below its everyday price, never fabricated.
  • Full product detail on demand. Switch on detail enrichment for the complete description, feature bullets, specifications, identifiers, image gallery and variant matrix, or leave it off for a faster, cheaper listing-only run.
  • Ratings and reviews. Every product already carries the aggregate rating and review count; detail enrichment adds the star-rating distribution and individual customer reviews.
  • Availability by state. See which Australian states a product ships to and whether it is available in-store or online.
  • Built for schedules. Incremental mode returns only new and changed products on recurring runs, and Resume skips products a previous run already collected (see the limits under Resume and recurring updates).

Use Cases

  • Price monitoring and repricing tools: track price, was-price, savings and specials on a schedule to react to Officeworks' pricing moves.
  • Comparison shopping and catalog aggregators: build a structured Officeworks product catalog with identifiers, categories and images.
  • Review and reputation analytics: collect ratings, review counts and individual reviews to gauge customer sentiment per product or category.
  • Merchandising and market research: monitor the Deals and Clearance collections to track what Officeworks is promoting and when.
  • Availability tracking: watch stock by state and online availability for a specific set of SKUs.

Data You Get

Sample shape: values are illustrative placeholders, not from a live record.

FieldExample
sku"SAMPLE0001"
name"Sample A4 Display Book 20 Pocket"
title (canonical, brand-prefixed)"Sample Brand A4 Display Book 20 Pocket"
brand"Sample Brand"
urlOfficeworks link to the product
price12.5
edlpPrice12.5 (the everyday price)
wasPrice / savingsAmount / savingsPercentnull unless the product is genuinely reduced below edlpPrice
isOnSpecial / promoLabeltrue / "Deal" (also "Clearance", "Low Price", "New", "Planet Positive")
isNew / isClearancefalse / false
currency / gstRate"AUD" / 10.0
gtin / manufacturerPartNumber / productTypeproduct identifiers
availableStates["NSW", "VIC", "QLD"] (an index flag from the product listing, not a delivery answer)
isAvailableInStore / availableOnlinetrue / true (index flags too; use the home delivery check below to see whether a product can be delivered to your postcode). The three index flags come from the product listing only: a pasted product link (/p/...) is read from its product page, so its rows (full and compact) do not carry them
deliveryPostcode / deliveryZone / deliveryType / deliveryAvailable / deliveryQuantity / deliveryEtaFrom / deliveryEtaTo / clickAndCollectQuantity / inStoreQuantity / deliveryStoreId / deliveryStoreName / deliveryChangeshome delivery check only: "4209" / "Regional" / "standard" / true / 6 / "2026-11-03" / "2026-11-03", then the click & collect and in-store counts at the store named in the same row; deliveryChanges appears with incremental mode only
variants / variantAxes / variantCountthe variant matrix (per-variant sku, url, attributes, thumbnail)
category / categories / categoryHierarchy"Display Books" / breadcrumb path / full category chains
shortDescription / longDescription / featuresproduct copy and feature bullets (detail enrichment)
specifications / attributesname/value pairs (detail enrichment)
imageUrl / imagesprimary image / full gallery
rating / reviewCount4.3 / 42, present on every product
ratingDistribution / recommendedCountstar breakdown and recommended count (detail enrichment)
reviews[]per-review rating, title, body, author, ISO-8601 date, helpful count, pros/cons and purchase location (detail enrichment)
reviewsCollectedhow many reviews were actually collected for this product
changeType / changedFields / firstSeenAt / lastSeenAt / previousPrice / priceChanges / variantChangesincremental mode only: "UPDATED" / ["price"] / dates / 12.5; priceChanges and variantChanges on compact rows only, see below

Every record also carries seoPath, urlKeyword, colour, multipackSize, unitsPerPack, brandUrl, itemsPerUnit, totalUnits, categoryPath, metaDescription, hasBusinessPrice, ratingScale, searchMode ("search" or "url"), source ("officeworks.com.au") and country ("AU"). Detail enrichment also adds description (long description, falling back to the short one) and breadcrumbs (the category trail as a list of titles). rangedOnline, rangedRetail and published are availability flags that appear as true/false when the site reports them and stay null otherwise. Products with no reviews leave rating, reviewCount, ratingDistribution and reviews absent rather than faked; reviewsCollected then reads 0.

Dataset views. The dataset has four views: an overview of every product, a detail view (description, specifications, gallery, variants), a reviews view, and a changes view for incremental runs (status, changed fields and the compact change sections). Every run also stores an OUTPUT summary in its key-value store (summary only, never extra dataset fields): productsReturned, scanned, reachedEndOfResults, scanComplete (no cap, cost limit, time limit, Resume or restart cut the scan short, so a missing product could be marked EXPIRED; it does not guarantee that every listed product was read: a product page that could not be read, or a search that stopped early on overlapping results, can still be missed), stateCommitted (true when the saved state was updated, false when another run had saved it first, null outside incremental mode), stoppedEarlyReason (costLimit, timeLimit, maxProducts, halted or null), expiredReturned, and with the delivery check on delivery: productsLookedUp, answered, noAnswer by cause (noResponse, refused, notFound, serverError, unreadable, unknownProduct), skippedByBreaker and attemptsByConnection (requests per connection type; residential requests are billed to your proxy plan). The log ends with the same delivery line.

How to Use

  1. Pick a mode: search (keyword and filters) or url (paste product, search, category or specials links).
  2. In search mode, add one or more keywords, or pick a Specials / offers collection (Deals or Clearance) instead of (or alongside) your keywords. Apply Brand, price, rating and sort filters as needed.
  3. Turn Fetch full product detail + reviews on (the default) for the complete description, specifications, gallery and customer reviews, or off for a faster, listing-only run.
  4. Set Max products to control run size and cost, then click Start.

Search by keyword with filters:

{
"mode": "search",
"queries": ["notebook"],
"brand": "Sample Brand",
"minPrice": 20,
"maxPrice": 200,
"minRating": 4,
"detailEnrichment": true,
"maxItems": 50
}

Browse the Clearance collection, cheapest first:

{
"mode": "search",
"queries": [],
"specialsCategory": "clearance",
"sortBy": "priceAsc",
"maxItems": 100
}

Accepted link shapes in url mode:

Link shapeExampleWhat you get
Keyword search/shop/officeworks/search?q=laptopevery page of results
Search starting at a page/shop/officeworks/search?q=laptop&page=2results from that page onward
Category (nested path)/shop/officeworks/c/office-supplies/pens/ballpoint-pensevery page of the category
Category starting at a page.../ballpoint-pens?page=2results from that page onward
Top-level category/shop/officeworks/c/art-suppliesevery page of the category
Product page/shop/officeworks/p/<product-slug>the one product
SKU as keyword/shop/officeworks/search?q=IMMM6162products matching that SKU
Deals / clearance/shop/officeworks/deals, /shop/officeworks/clearancethe deals or clearance collection (these links redirect home on the site, so the collection is browsed directly)
SKU-only product link/shop/officeworks/p/IMMM6162a link that ends in just the SKU is resolved through SKU search (the same product is returned), with a warning in the log if nothing matches
Host without wwwhttps://officeworks.com.au/...treated as the www host

A link that is none of these is reported with a warning in the log and returns nothing for that link.

The default URLs list holds one example of each shape above (ten links). Max products is a cap on the whole run, so with the default of 20 only the first link or two are reached; raise Max products, or replace the list with your own links, before running at scale.

Paste a single product link:

{
"mode": "url",
"urls": ["https://www.officeworks.com.au/shop/officeworks/p/sample-product-sku"],
"detailEnrichment": true,
"maxReviewsPerProduct": 30
}

Track a category daily: selling price, stock and delivery to 4209, compact changes only:

{
"mode": "url",
"urls": ["https://www.officeworks.com.au/shop/officeworks/c/office-supplies/pens/ballpoint-pens"],
"incrementalMode": true,
"changeScope": "sellingPriceAndStock",
"compactUpdates": true,
"deliveryPostcode": "4209",
"verifyHomeDelivery": true,
"emitExpired": true,
"expireAfterMissedScans": 2,
"maxItems": 0,
"maxPages": 0
}

The first run returns every product in full as NEW, with its delivery answer for 4209. Later runs return a product only when its selling price moved, its stock flags changed, a size or colour joined or left its family, or the answer to "can it be delivered to 4209" flipped; each comes back as a short UPDATED row with the old and new values in priceChanges, variantChanges and deliveryChanges. Unchanged products are not returned or billed. A product missing from two complete scans in a row comes back once as a short EXPIRED row.

Which field holds what (daily monitoring with delivery):

You needFieldNotes
Product IDskuPresent on every row, full and compact.
Variant IDsvariants[].skuOn NEW rows. On a compact UPDATED row, a size or colour that joined or left the family is listed in variantChanges with its sku and change (ADDED / REMOVED / CHANGED).
Current payable pricepriceThe selling price, on every row. previousPrice and priceChanges.price hold the old value when it moved.
Delivery availabilitydeliveryAvailabletrue / false is a confirmed answer for deliveryPostcode; null means the answer could not be read this run.
BarcodegtinText with leading zeros kept. On NEW rows and on compact UPDATED / EXPIRED rows. A few products have none.

Both samples below are illustrative placeholders, abridged.

A NEW row (first run, complete record):

{
"sku": "SAMPLE0001",
"name": "Sample A4 Display Book 20 Pocket",
"brand": "Sample Brand",
"url": "https://www.officeworks.com.au/shop/officeworks/p/sample-a4-display-book-sample0001",
"gtin": "9300000012345",
"price": 12.5,
"currency": "AUD",
"variants": [{ "sku": "SAMPLE0001", "attributes": { "Colour": "Blue" } }],
"deliveryPostcode": "4209",
"deliveryZone": "Regional",
"deliveryType": "standard",
"deliveryAvailable": true,
"deliveryQuantity": 6,
"changeType": "NEW",
"changedFields": [],
"firstSeenAt": "2026-11-04T01:00:00Z",
"lastSeenAt": "2026-11-04T01:00:00Z"
}

An UPDATED compact row (a later run: the price dropped and delivery flipped):

{
"sku": "SAMPLE0001",
"name": "Sample A4 Display Book 20 Pocket",
"brand": "Sample Brand",
"url": "https://www.officeworks.com.au/shop/officeworks/p/sample-a4-display-book-sample0001",
"gtin": "9300000012345",
"currency": "AUD",
"price": 9.0,
"changeType": "UPDATED",
"changedFields": ["price", "deliveryChanges"],
"firstSeenAt": "2026-11-04T01:00:00Z",
"lastSeenAt": "2026-11-05T01:00:00Z",
"previousPrice": 12.5,
"priceChanges": { "price": { "old": 12.5, "new": 9.0 } },
"variantChanges": null,
"otherChanges": null,
"deliveryPostcode": "4209",
"deliveryAvailable": false,
"deliveryChanges": { "deliveryAvailable": { "from": true, "to": false } }
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/officeworks-scraper-lite").call(run_input={"mode": "search", "queries": ["notebook"]})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
print(product["name"], product["price"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/officeworks-scraper-lite').call({ mode: 'search', queries: ['notebook'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Multiple keywords and specials in one run

Every keyword (and a specials collection, if chosen) is searched as its own target within the run, one after another, sharing the same overall dedup set. Pasted search and category links in url mode work the same way. That shared set has three real, confirmed side effects worth knowing:

  • Overlap is returned once. A product returned by an earlier target is never returned again by a later one in the same run, so the run total can be lower than the sum of each target searched on its own.
  • A later target can stop on its very first page. A target stops walking as soon as it reaches a page on which every product has already been handled. That check uses the run-wide set, so if a later target's first page is made up entirely of products an earlier target already handled (two near-synonymous keywords such as "pen" and "pens", for example), that target stops right there and its later pages, including products no earlier target returned, are never read in that run.
  • Products skipped by a cap still count as handled. When a page has more candidates than the remaining room (see below), the ones beyond the room are marked as handled without being returned. A later target in the same run then silently skips those products too, and can stop early on them as described above.

In incremental mode with emitExpired on, a target that stopped early this way still counts as fully scanned, so tracked products on its unread pages can be returned (and billed) as EXPIRED even though they are still listed; they come back as REAPPEARED on a later run that reads them. If you need every target's own complete result set, or you use emitExpired, run overlapping searches as separate actor runs (each with its own automatic state) instead of combining them into one queries or urls list.

How Max products and Max pages shape a run

Max products (maxItems) stops the whole run once that many products have been returned; Max pages (maxPages) caps how many result pages are walked per keyword or URL (0 = walk every page). On a capped run, a page's worth of candidates beyond the remaining room is not inspected in this run. If the platform restarts a run (server migration or Resurrect), the products it saved before the restart count toward Max products, so the run as a whole never returns more than that many products.

Incremental-mode tip: the remaining room for a page is computed before incremental mode knows which candidates will turn out UNCHANGED and get suppressed. Suppressed products still use up slots, so with a result page of up to 48 products and a maxItems below that (the default is 20), a new or changed product later on the same page can be skipped in this run even though the run ends well under maxItems. Worse, with emitExpired on: because suppressed products are not returned, such a run can still finish under maxItems and count as a complete scan, and then every tracked product that was skipped this way is returned (and billed) as EXPIRED even though it is still listed. Those products come back as REAPPEARED (billed again) on a later run that reaches them. Set maxItems to 0 (and leave maxPages at 0) for incremental runs, especially with emitExpired on.

Check home delivery to a postcode

Set Delivery postcode (four digits, for example 4209) and turn on Check home delivery live. For every returned product the actor asks Officeworks, at that moment, whether the product can be delivered to that postcode, and adds:

  • deliveryType: the delivery speed label the site reports (for example standard, extended, nextDay), nearest when only store stock exists and nothing can be delivered, or outOfStock.
  • deliveryAvailable: true only when the type is not outOfStock and the delivery quantity is above zero.
  • deliveryQuantity, deliveryEtaFrom, deliveryEtaTo (ISO dates), and deliveryZone for the postcode (Metro, Regional, Country). A deliveryQuantity of 1,000,000,000 or more is the site's marker for "no stock limit" (seen on some items), not a real count: it is passed through unchanged, and deliveryAvailable is still true for it.
  • clickAndCollectQuantity and inStoreQuantity at the store shown in deliveryStoreId / deliveryStoreName. By default that is the store whose own postcode is closest to yours; set Pickup store (for example W444) to choose another. Delivery type, quantity and arrival dates did not depend on the store in our checks of in-stock items.

A product whose answer could not be read gets null delivery fields and is never reported as unavailable: the site returned nothing usable, an error or a page for another product, kept refusing the request, or answered for a SKU it does not know (the site answers an unknown SKU as "out of stock" without the product details a real answer always carries, so such an answer is treated as not read, not as out of stock). An answer that could not be read is retried a few times on a fresh connection before it is given up. Each product is looked up once per run, with a small number of lookups in flight at a time; if lookups keep failing, most of the remaining ones are skipped for that run and an occasional one still tries, so the check resumes if the service recovers. An answer about a SKU the site does not know is not a failure for that purpose: the service is answering, so the check carries on for the products after it. The check is off by default, and a run without it is unchanged. In incremental mode only the yes/no answer (deliveryAvailable) is compared, for the same postcode: stock counts, the speed label (which changes with the time of day), arrival dates and the store-specific counts move without the answer changing, so they never make a product count as changed. When the answer flips, an otherwise unchanged product is returned as UPDATED with the flip listed in deliveryChanges. The first answer that can be read for an already tracked product (its earlier lookups could not be read, the check was just turned on, or the postcode changed) is returned once as UPDATED with deliveryChanges {"deliveryAvailable": {"from": null, "to": true}} (or false), so you always receive every product's first known answer; an answer that was known and then could not be read for a run is not reported again when it is read again. Changing only the pickup store starts no new comparison. A delivery postcode needs the check turned on, and the run stops with a message otherwise. A pickup store without the check is ignored (a warning is logged). The lookups consume additional platform usage; there is no enrichment event fee. Turning the check on for a search that is already tracked, or changing the delivery postcode, therefore returns every tracked product once (one product row each, see How much does it cost?).

Using the rows in your own system

You seeIt means
A full row (changeType NEW, UPDATED, REAPPEARED or UNCHANGED)The whole product record as read this run.
A compact row (Compact update rows on)Only identifiers, current price and stock, and the change sections; keep your stored copy of the other fields.
deliveryAvailable true / falseThe site answered this run: deliverable / not deliverable to the postcode.
deliveryAvailable nullThe answer could not be read this run (it does not mean unavailable). deliveryPostcode, deliveryZone and the store fields are filled even then, so do not use them to tell whether the answer was read.
No gstRate / ratingScale on a rowThe product page could not be read (or Detail enrichment is off): the row holds the search result only.

Joining with Kmart data. The Kmart Australia Scraper returns apn as a number (leading zeros dropped), while this Actor returns gtin as text with its leading zeros kept (for example "071641171725"). Join on the number (int(gtin) == apn), or pad both to 14 digits. A few Officeworks products have no gtin.

Resume and recurring updates

There are two different things here, pick the one that matches what you're doing:

NeedUse
A crawl stopped and should continueresumeFromRunId
Run the same search every day and receive only changesincrementalMode
Keep separate daily campaigns for similar searchesdistinct stateKey values
Run a normal full snapshotleave both off

Resume (resumeFromRunId) continues one specific interrupted or previous crawl: paste a run ID or dataset ID and this run skips products already collected there, returning only the remaining new products. Two real, confirmed limits:

  • It can return nothing if the earlier run collected a whole first page. The resumed walk starts again from page 1, and a page on which every product was already collected ends that search or link. So if the earlier run returned every product on the first result page of a keyword or link (up to 48 products), the resumed run stops there and returns nothing for it, with a "No products matched" status, even though later pages were never collected. Resume works as intended only when the earlier run stopped part-way through the first page; otherwise re-run the search with a larger Max products instead.
  • Pasted product links are not skipped. In url mode, a pasted product link (/p/...) is matched by its link text rather than by its SKU, so resuming a run that already returned it fetches and returns (and bills) that product again. Remove already-collected product links from urls before resuming.

Incremental mode (incrementalMode) is for a schedule (for example, daily): the actor remembers the previous run of the same search by itself, so you never paste a run ID. The first run returns everything as NEW. Later runs return only NEW, UPDATED and REAPPEARED products by default; unchanged products are suppressed unless emitUnchanged is on. Change detection (UPDATED) compares the product's own data: price, specials and promo label, stock and availability, name, brand, category, images and the other catalogue fields. The order of availableStates and of the size/colour family (variants) is not compared (the site returns the same states in varying order): only which states and which sizes/colours, and the rows you receive keep the site's order. rating, reviewCount, ratingDistribution, recommendedCount, reviewsCollected and reviews are returned but never cause UPDATED, because other customers change them without the product changing. Use ignoreFieldsForChanges to leave out further fields; changing that list does not turn the whole baseline into UPDATED. A run that started from a pasted ?page=N URL with N above 1, or where Max products trimmed a page, does not count as a full scan and never returns EXPIRED rows. Turn on emitExpired to also get a tombstone row for a product that was tracked before but is no longer found; this only happens once a run counts as having fully scanned the tracked search (not when Max products or Max pages capped it, and not on a Resume run). A run can count as complete while still having skipped some listed products, so read the tips below and under Multiple keywords and specials in one run and How Max products and Max pages shape a run before relying on EXPIRED.

State is isolated per mode/search/URL/specials/brand/sort/price/rating/stock setup automatically; set stateKey to name a campaign, or to deliberately share state across differently-configured runs. Switching Detail enrichment on or off for a search with no stateKey continues from the newest saved baseline of that search under either setting (also when you switch back and forth), so the search is not returned as NEW again and a change already returned under one setting is not returned again under the other. The search result and the product page are compared source by source: the price shown in search results can differ from the product page's price, so a search-result price is only ever compared with an earlier search-result price, and a product-page price with an earlier product-page price.

What counts as a change (changeScope): all (the default) compares the whole product record as described above. priceAndStock compares only the price fields (price, edlpPrice, wasPrice, savingsAmount, savingsPercent, isOnSpecial, isClearance), the stock flags (availableOnline, availableStates, isAvailableInStore) and the sizes and colours in the product family (variants, by SKU). sellingPriceAndStock is the same but counts a price change only when price, the price a buyer pays today, moves. At Officeworks every size or colour is its own product with its own SKU, price, stock and delivery answer, so its changes come back on its own row; within a product, variantChanges reports a size or colour that joined (ADDED) or left (REMOVED) the family. With Check home delivery live on, a flip of deliveryAvailable returns an UPDATED row in every option (changedFields then names deliveryChanges); arrival dates, quantities and the speed label never do. The option never changes the automatic state key: switching it keeps your saved baseline, and nothing is returned again because of the switch.

Compact update rows (compactUpdates): UPDATED and EXPIRED rows carry only sku, name, brand, url, gtin (when the product has one), currency, the current price and stock flags (none for a pasted product link, see the output table), changeType, changedFields, firstSeenAt, lastSeenAt, previousPrice, priceChanges (old and new value of each moved field, for example {"price": {"old": 12.5, "new": 9.0}}), variantChanges (a size or colour ADDED to or REMOVED from the family and, with "All fields", one whose label, link or image moved, as CHANGED with its old and new values), otherChanges (old and new value of every other moved field, for example the description, so a compact UPDATED row always says what changed) and, with the delivery check on, the delivery fields and deliveryChanges. NEW rows always carry the complete record. Full UPDATED rows also carry previousPrice (the price before this run, empty when the price did not move). With "All fields" and compact rows, the full record of each compact UPDATED row is also saved in the run's own key-value store (INCREMENTAL_RECOVERY_<sku>) just before the row is saved, so a run restarted by the platform does not report the same change again on the next run; only a restart of that run reads it.

Missing products (emitExpired, expireAfterMissedScans): a product is returned as EXPIRED only after it has been missing from expireAfterMissedScans complete scans in a row (default 1); a product that comes back resets the count. Scans that were not complete (capped by Max products or Max pages, a Resume, a restarted run, a product link whose page could not be read) never count as a miss. A product that is still listed but out of stock is not EXPIRED: that shows in the stock and delivery fields.

Checks that could not be read: a delivery answer that could not be read leaves the delivery fields null, is never reported as unavailable, never counts as a change; the last known answer is kept for the next comparison. A product page that could not be read on a run returns the search result only. That row is compared only on the fields that come from the search result on both runs (isClearance, availableOnline, availableStates, isAvailableInStore); the saved product-page values, including its price, are kept untouched, and a price or detail change is reported on the next run that can read the page.

Start from an existing dataset (baselineDatasetId): if you already scraped the search with this Actor, pick that run's dataset and your first incremental run returns only products that changed since then. Pick it unedited (rows that do not look like this Actor's products stop the run before scraping, with a message saying why) and from a run with the same search and detail setting. Compact rows and EXPIRED rows in it are skipped. It is only used while the state key has no saved state, and cannot be combined with Resume.

Coming from the Officeworks Australia Scraper? If you tracked the same State key (or the same search settings) there, pick that scraper's history store (officeworks-com-au-incremental-state) in "Continue from the Officeworks Australia Scraper's history" and the first Lite run continues from that saved history, so you are not given the whole search as NEW again. With no State key, the history saved there under either Detail enrichment setting of the same search is used. Your earlier history is only read, never changed, and nothing needs to be copied or run. It is used only on the first Lite run for that search (not once Lite already tracks it, and not with a starting dataset or Resume). If the picked store cannot be read, is not that scraper's history store, or holds something that is not its saved state, the run stops before scraping with a message saying why, instead of returning everything as NEW; if it simply has nothing saved for this search, the run says so and starts fresh.

Tips (real, confirmed behavior worth knowing before you rely on this):

  • emitExpired is not capped by Max products. Once a run proves it scanned the tracked search completely, every previously-tracked product that is no longer found is returned as EXPIRED in one batch, regardless of maxItems. If a monitored search shrinks a lot between runs (a big Clearance collection selling out, for example), the EXPIRED batch on that run can be much larger than your usual maxItems, and is billed accordingly. Turn emitExpired off, or expect and budget for an occasional larger run, if this matters to your costs.
  • Combining resumeFromRunId with a brand-new incrementalMode campaign bills one phantom UPDATED per resumed product on the very next scheduled run. Resuming into a fresh (first-ever) incremental baseline seeds that baseline from the resumed dataset without re-fetching those products, so their stored fingerprint is not yet known. The next scheduled run then reports every one of those products as UPDATED with every field listed under changedFields, even if nothing actually changed. It happens exactly once per product, right after that bootstrap; from then on comparisons are against a real fingerprint. If you don't want this, run a plain (non-incremental) resume first, then start incremental mode fresh from a normal full run.
  • Reusing the same stateKey for a materially different search is a real feature, but it can bill unrelated products as EXPIRED. If you deliberately point the same stateKey at a new keyword, URL or filter set, every product tracked under the old configuration that isn't rediscovered by the new one looks exactly like a product that disappeared, and (with emitExpired on) is billed as EXPIRED the moment a run under the new configuration completes a full scan. Only share a stateKey across scopes that are meant to be tracked together; leave it empty for anything else so each distinct search gets its own isolated, auto-derived state.
  • A run restarted by the platform (server migration or Resurrect) never returns EXPIRED rows. The restarted run continues from its saved position and skips the keywords, links and pages it had already finished, so it cannot tell which tracked products are really gone. It therefore skips EXPIRED detection for that run, and the products it had already delivered before the restart are kept in the saved state, so they are not returned as NEW or UPDATED again.
  • A run that is stopped outright (it hit its timeout or ran out of memory) and is NOT resurrected. About 75 seconds before its time limit (or, for a run with a short time limit, a quarter of it) a run stops starting new products, saves what it delivered and ends with a status message, so the next run continues normally (it is not a complete scan, so it returns no EXPIRED rows). If a run is killed anyway before it could save, the next run of the same search adds the products that run had already returned to the saved state before it starts, so they are not returned or billed again. This needs to read that run's dataset and storage: when it has been deleted, or this run is not allowed to read it, the earlier run is skipped with a log line and those products can come back once; if reading it keeps failing for another reason, the run stops without saving or billing anything, and after three such runs the earlier run is given up the same way. This also holds when an older, already finished run is resurrected after newer runs: the saved state records which run wrote each product, so a product that a newer run has since saved (changed, unchanged or EXPIRED) keeps that newer answer and is not returned or billed again. To do this, an incremental run that returns at least one product writes one small INCREMENTAL_REPLAY_BASES record to its own key-value store. Products last saved by a version of this Actor from before this ordering existed have no such record, and are treated as the oldest. If that record cannot be saved (the run's storage keeps refusing the write after retries), the run stops before saving any further product and fails with a message saying so: nothing it could not order is saved or billed, and those products are returned by the next run. Likewise, if the saved state or the run's restart records cannot be read (storage keeps failing after retries, or returns something unreadable), the run stops before it saves, bills or changes anything, keeps its restart point and fails with a message; resurrect it (or run again) once storage is reachable. A saved state that cannot be read is never treated as empty, so it never returns the whole search as NEW. A product returned as EXPIRED just before a restart stays expired after it, so it is not returned as EXPIRED a second time, and comes back as REAPPEARED if it is listed again.
  • Do not run the same search with different Detail enrichment settings at the same time. Two simultaneous runs of one search, one with Detail enrichment on and one with it off, keep separate saved states that are reconciled afterwards. When their saves happen at the same moment, the run with product pages wins a tie, so if the other run saw a later change, that one change can be returned (and billed) once more by the next run. Runs that follow one another, in any order and with either setting, are not affected.

Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify โ†’ Settings โ†’ Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch (keyword + filters) or url (scrape pasted URLs).
queriesarray["laptop"]One or more keywords to search (search mode). Each keyword is searched separately.
specialsCategorystring(none)Browse deals or clearance instead of (or alongside) your keywords.
urlsarrayone example of each link shape (see table below)Product, search, category, deals or clearance URLs to scrape (url mode).
brandstring(none)Keep only products whose brand matches this value.
sortBystringrelevancerelevance, newest, nameAsc, priceAsc, priceDesc, ratingAsc, or ratingDesc.
minPriceinteger(none)Only keep products priced at or above this amount (AUD).
maxPriceinteger(none)Only keep products priced at or below this amount (AUD).
minRatinginteger(none)Only keep products with an average rating at or above this value (1-5).
includeOutOfStockbooleantrueInclude products not currently available in-store or online.
detailEnrichmentbooleantrueFetch full product detail, specifications, gallery and reviews (one extra request per product).
maxReviewsPerProductinteger20Cap on reviews collected per product when detail enrichment is on (0 = all available, capped at 1000 per product; values above 1000 are treated as 1000).
maxItemsinteger20Maximum number of products to return across the whole run (0 = unlimited).
maxPagesinteger0Safety cap on result pages walked per keyword / URL (0 = walk every page).
resumeFromRunIdstring(none)ID of a previous run (or dataset) to continue from; already-collected products are skipped.
incrementalModebooleanfalseReturn only new and changed products on scheduled runs.
stateKeystring(none)Name or share an incremental-mode state. Left empty, a key is derived automatically from the search/filter setup.
emitUnchangedbooleanfalseAlso return (and bill) unchanged products (incremental mode only).
emitExpiredbooleanfalseAlso return (and bill) products no longer found (incremental mode only).
baselineDatasetIdstring(none)Incremental mode only. Pick the dataset of an earlier run of this Actor as the starting point, so the first incremental run returns only changes. Not combinable with Resume.
earlierHistoryStoreIdstring(none)Incremental mode only. Pick the Officeworks Australia Scraper's history store (officeworks-com-au-incremental-state) so the first Lite run for a search continues from that history. Read only.
changeScopestringallIncremental mode only. all, priceAndStock (price fields, stock flags, family sizes and colours) or sellingPriceAndStock (only price among the price fields). Switching keeps the saved baseline.
compactUpdatesbooleanfalseIncremental mode only. Short UPDATED and EXPIRED rows with priceChanges, variantChanges and deliveryChanges; NEW rows stay complete.
expireAfterMissedScansinteger1Incremental mode only, with emitExpired. Complete scans in a row a product must be missing before it is EXPIRED (1 to 30).
ignoreFieldsForChangesarray[]Extra field names (or dotted paths such as images.url) left out of change detection (incremental mode only). They are still returned, just not compared.
deliveryPostcodestring(none)Four-digit postcode for the home delivery check. Required when the check is on.
verifyHomeDeliverybooleanfalseCheck live whether each returned product can be delivered to the delivery postcode.
pickupStoreIdstring(none)Optional store code (for example W444) for the click and collect and in-store quantities. Ignored unless Check home delivery live is on.
proxyobjectApify ProxyConnection settings.
mcpConnectorsarray(none)Optional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstring(none)Notion connector only: page under which records are created.
maxNotifyListingsinteger50Cap on items written to each connector per run.

Output Example

Sample shape: values are illustrative placeholders, not from a live record.

{
"sku": "SAMPLE0001",
"name": "Sample A4 Display Book 20 Pocket",
"title": "Sample Brand A4 Display Book 20 Pocket",
"brand": "Sample Brand",
"url": "https://www.officeworks.com.au/shop/officeworks/p/sample-a4-display-book-sample0001",
"price": 12.5,
"edlpPrice": 12.5,
"wasPrice": null,
"savingsAmount": null,
"savingsPercent": null,
"isOnSpecial": true,
"promoLabel": "Deal",
"isNew": false,
"currency": "AUD",
"gstRate": 10.0,
"gtin": "9300000012345",
"manufacturerPartNumber": "SB-DB20",
"productType": "Display Books",
"availableStates": ["NSW", "VIC", "QLD"],
"isAvailableInStore": true,
"availableOnline": true,
"isClearance": false,
"category": "Display Books",
"categories": ["Office Supplies", "Folders & Filing", "Display Books"],
"categoryHierarchy": [["Office Supplies", "Folders & Filing", "Display Books"]],
"variantAxes": { "Colour": ["Blue", "Black"] },
"variantCount": 2,
"variants": [
{
"sku": "SAMPLE0001",
"url": "https://www.officeworks.com.au/shop/officeworks/p/sample-a4-display-book-sample0001",
"attributes": { "Colour": "Blue" },
"imageUrl": "https://www.officeworks.com.au/images/sample-thumb.jpg"
}
],
"shortDescription": "A durable display book for everyday filing.",
"features": ["20 pockets", "A4 size", "Reinforced spine"],
"specifications": { "Brand": "Sample Brand", "Pockets": "20" },
"imageUrl": "https://www.officeworks.com.au/images/sample.jpg",
"images": ["https://www.officeworks.com.au/images/sample.jpg"],
"rating": 4.3,
"reviewCount": 42,
"ratingDistribution": { "5": 20, "4": 12, "3": 6, "2": 2, "1": 2 },
"recommendedCount": 35,
"reviews": [
{
"reviewId": "SAMPLE-REVIEW-1",
"rating": 5,
"title": "Handy for the office",
"body": "Good quality and holds up well.",
"author": "SampleReviewer",
"date": "2026-03-01T09:00:00.000+00:00",
"helpfulCount": 2
}
],
"reviewsCollected": 1
}

Plan Requirement

The default proxy setting works out of the box on datacenter connections. Officeworks occasionally refuses a single request from one exit (a plain "request blocked" page); the connection rotates to a fresh exit and retries automatically, and the same pages load on the retry. Residential gives more headroom for large or frequent runs; pick it under Connection. If no page can be read at all, the run fails with a clear message instead of an empty dataset: a refused proxy connection for the account points to a plan without proxy access, and otherwise the message says the requests failed and to try again later.

FAQ

How much does it cost?

You pay one product event per returned dataset row and one run-start event per run, plus Apify platform usage for compute, proxies, storage and data transfer. The Pricing tab shows the current rates for your plan. This Lite version has no detail enrichment or delivery enrichment event fees. Product pages, reviews and the home delivery check still consume platform resources. Use Max products, Max pages, Max reviews per product and the platform run limits to bound work. If you also set a maximum cost per run, the scraper stops as soon as that budget is used up and keeps every product already saved. A delivery lookup for a SKU the site does not know is treated as not read. The first readable delivery answer of an already tracked product (also when you turn the check on for a tracked search, or change the postcode) returns that product once, as one product row. A product whose states with stock or sizes/colours only changed order is no longer returned.

This actor collects only publicly available product data. You are responsible for how you use it: follow Officeworks' terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices and specifications are generally facts, but product photos and review text may be subject to third-party rights.

Can I get only new or changed products on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared products, and unchanged ones are suppressed by default.

Why did my run return fewer products than Max products, or a very large batch when Emit expired is on?

Max products stops a run once that many products have been returned; a capped page or a capped run does not go back for more. A run with several keywords or links can also stop a later one early when its first page overlaps products already handled, and an incremental run spends slots on unchanged products it then suppresses; see Multiple keywords and specials in one run and How Max products and Max pages shape a run. Separately, emitExpired is not limited by Max products: once a run fully scans the tracked search, every previously-tracked product no longer found is returned as EXPIRED in one batch, which can be larger than your usual Max products if the tracked search shrank a lot since the last run. See the tips under Resume and recurring updates.

Why does a product I resumed show up as UPDATED on the very next incremental run, even though nothing changed?

This happens exactly once, only when resumeFromRunId is combined with a brand-new incrementalMode campaign (no prior state for that stateKey). The resumed products are added to the new baseline without a real fingerprint yet, so the first comparison against them reports every field as changed. It does not repeat on later runs. See the tips under Resume and recurring updates.

Why did my run fail instead of returning an empty dataset?

If the connection is refused before any data could be read, the run stops with a clear message so "no products found" is never confused with "nothing could be read". Run it again in a few minutes. Separately, starting Incremental mode with Resume when that state key already has tracked products also fails on purpose, with a message telling you to drop Resume or pick a different State key, rather than silently mixing the two.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

๐Ÿ”— Want more Officeworks data?

Pair this actor with these related scrapers from the same team:

๐Ÿ›’ Officeworks Scraper
Scrape Officeworks (officeworks.com.au) products: name, brand, title, price, GST...
๐Ÿ›’ Myer Scraper
Scrape Myer (myer.com.au) department-store products: name, brand, price, was-price...
๐Ÿ›’ BIG W Marketplace Scraper
Scrape BIG W Marketplace seller listings from bigw.com.au: name, brand, price, was-price...
๐Ÿ›’ Harvey Norman Scraper
Scrape Harvey Norman Australia products: name, brand, price, was-price / discount...
๐Ÿ›’ Amazon Australia Product & Reviews Scraper
Scrape Amazon Australia (amazon.com.au) products and customer reviews. Search by keyword...
๐Ÿ›’ Costco AU Scraper
Scrape Costco.com.au products by keyword or product, search and category URLs. Extract...

๐Ÿ‘‰ Browse all abotapi scrapers

๐Ÿ’ฌ Support

  • ๐Ÿž Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • ๐Ÿ› ๏ธ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • โญ Enjoying it? A quick review on the actor page helps other users find it.