Sainsbury's Grocery Price & Promotion Watch (UK)
Pricing
from $0.84 / 1,000 product scrapeds
Sainsbury's Grocery Price & Promotion Watch (UK)
Search Sainsbury's groceries by keyword and get one flat row per product: price in GBP, unit price (per kg or litre), was-price, every promotion and Nectar offer with its dates, rating, review count, categories, image and URL. Sort by price or rating. For UK grocery price and promotion watching.
Pricing
from $0.84 / 1,000 product scrapeds
Rating
0.0
(0)
Developer
Superslow Sloth
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
7 days ago
Last modified
Categories
Share
Search Sainsbury's groceries for one or more terms and get one flat row per product: the shelf price in pounds, the unit price (per litre, kilo or each), the was-price when a promotion lowers the price, every promotion with its wording and start and end dates, whether a Nectar card is needed, rating, review count, categories, barcodes, image and the product URL. Run it on a schedule and diff the dataset to get a UK grocery price-change and promotion watch, or read a whole aisle for price comparison against Tesco, Asda and Aldi.
No account, no API key, no login, no browser. One request returns 60 products.
Input
| Field | Notes |
|---|---|
searchTerms | One or more search terms, one per line. Required. A product found by two terms is returned once, under the first. Sainsbury's matches loosely: milk finds 963 products, including oat drinks and baby formula. |
maxItems | Products to read per search term (default 100, up to 9,900). 60 per page. |
sort | relevance (default), price-asc, price-desc, rating. |
proxyConfiguration | Apify proxy by default (see "How it fetches"). |
About the sorts
The sorts are Sainsbury's own and the actor checks each one: the response says
which sort it applied (controls.sort.active), and the actor fails a term (at
no charge) rather than return rows in an order that was not asked for. This
matters because Sainsbury's accepts some sort names it does not implement:
NAME_ASC is answered with HTTP 200 and the default order (measured).
- Featured products are pinned to the top of every sort. Sainsbury's paid
"Featured" placements (2 of the first 60 for
milk) come first whatever the order, so the first one or two rows of a price sort can be out of order. They are flaggedis_featured; drop them if you want a clean ranking. - The rating sort is Sainsbury's "Ratings - High to Low". It is not a strict sort on the star figure.
Output
Prices are numbers in pounds sterling; currency is always GBP.
| Field | Notes |
|---|---|
query | The search term that first found this product. |
product_uid, url, eans | Sainsbury's product id, the https://www.sainsburys.co.uk/gol-ui/product/<slug> link, and the barcodes it lists (EAN-13 plus Sainsbury's in-store codes). |
name, product_type, categories, labels | As Sainsbury's shows them. product_type is BASIC, LOOSE, CATCHWEIGHT or MULTIVARIANT (see below). labels are shelf badges such as Chilled, BRITISH, ALDI PRICE MATCH*. |
price, price_measure | The shelf price for one pack, with a simple promotion already applied ("Buy 1 for 2", "Save 1/3 with Nectar"). price_measure is kg for loose produce, null for an ordinary pack. |
unit_price, unit_measure, unit_measure_amount | The price per ltr, kg, ea or sht printed on the shelf label. Null when Sainsbury's sends none. |
was_price, was_unit_price | The price before a promotion, read from the promotion's original_price (and from original_unit_price). Null when no promotion lowers the price, never 0. |
on_promotion, nectar_offer | on_promotion is true when the record carries any promotion. nectar_offer is true when any of them needs a Nectar card; with a was_price it means the lower price is the Nectar price. |
promotions | A list of objects, one per promotion: strap_line (the shelf wording, "Meal Deal for £3.95", "Buy any 3 for £3.00 with Nectar"), type and group (simple, multi_buy, meal_deal), is_nectar, start_date and end_date (ISO, UTC), original_price, discount_percentage (only sent on percentage promotions, else null), promotion_uid, url. Empty list when there is none. |
options | CATCHWEIGHT and MULTIVARIANT products only: one object per weight band or size with label, price, unit_price, unit_measure and (variants) product_uid and promotions. Empty for the rest. |
average_weight, average_weight_measure | LOOSE produce: the weight of one piece (a banana is 0.17 kg). Null elsewhere. |
rating, review_count | Star rating out of 5 and the review count. Sainsbury's sends a rating of 0 when there are no reviews; that is null here, with review_count 0. |
is_featured, is_alcoholic, is_available | Sainsbury's own flags. Every one of 723 distinct products sampled was is_available: true; the search does not list what cannot be bought. |
image_url | The product photo. |
Honest nulls, and four product shapes
A field Sainsbury's did not send is null, never 0 or "".
Most products are BASIC. Two shapes have no shelf price of their own, and
for them price, unit_price and was_price are null:
CATCHWEIGHT(meat sold by weight band):optionslists each band with its price, for example a pair of chicken breast fillets at 7.14 for 200 g to 340 g and 10.08 for 340 g to 480 g, both at 21.00 per kg.MULTIVARIANT(sizes of one item, such as a mattress protector):optionslists each size with its own product id and price.
Promotions on a multivariant sit on each option; the parent carries none.
Pricing
Pay per event:
- $0.002 per run (
actor-start), charged only after your input is validated, so a run that fails on bad input costs nothing. - $0.00084 per product row written (
product-scraped), i.e. $0.84 per 1,000 products.
How it fetches
It calls the JSON API behind Sainsbury's own search page
(www.sainsburys.co.uk/groceries-api/gol-services/product/v1/product), 60
products per request, paging with page_number. Measured 2026-10-12: HTTP 200
and JSON from Apify datacenter exits, no key or cookie needed. A plain
residential VM was refused with HTTP 403 in the same test, which is why the
default proxy is on; a refusal (403, 429, 5xx) is treated as the address being
blocked, and the actor rotates to a fresh one and retries. An HTTP 400 is
permanent and is not retried. The measured walks: a 17-page search (963
products) came back in full with 963 distinct ids, and 71 requests over three
runs met no rate limit.
Things worth knowing, all measured:
- Depth ceiling. The count tops out at 10,000, and Sainsbury's will not page that deep: page 165 at 60 per page (9,900 products) answers and page 166 is a 400. The actor stops at 9,900 per term and logs any term that had more behind it; make the term more specific to see the rest.
- Past the end. A page past the last one is an empty 200, which ends the term.
- Loose matching. Sainsbury's matches words loosely, so a short term can return thousands of products and an empty term returns the whole catalogue. The actor refuses an empty term.
- Nectar prices. Nectar prices need a card at the till. The actor reports
them as Sainsbury's lists them, flagged with
nectar_offer; the shelf price for a Nectar promotion is the Nectar price. - Not offered. The page size is capped at 60 (a request for 90 or 200 is answered with 60), so there is no input for it. Offer and brand filters exist on the site but are not wired into this actor.