Pick n Pay Scraper - South African Grocery Prices & Promotions avatar

Pick n Pay Scraper - South African Grocery Prices & Promotions

Pricing

from $1.05 / 1,000 product scrapeds

Go to Apify Store
Pick n Pay Scraper - South African Grocery Prices & Promotions

Pick n Pay Scraper - South African Grocery Prices & Promotions

Pick n Pay scraper: pnp.co.za product search by term or category to one row per product with price in ZAR, was-price and savings, Smart Shopper promotions, badges, stock, rating, unit price, category path and image. For South African grocery price and promotion watching.

Pricing

from $1.05 / 1,000 product scrapeds

Rating

0.0

(0)

Developer

Superslow Sloth

Superslow Sloth

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Security

Limited permissions

Share

Pick n Pay scraper: South African grocery prices and promotions

This Pick n Pay scraper reads the product search of pnp.co.za and returns one flat row per product: the shelf price in rand (ZAR), the was-price and saving when the price is marked down, the Smart Shopper offers attached to the product (Combo For R49.99, 3 For R65.00) with their end date, the tile badges, whether it is in stock, rating and review count, the price per kg for products sold by weight, the category path, the supplier, the product link and the photo. Search by term, by category, or both, for any Pick n Pay store. Run it on a schedule and diff the dataset to get a South African grocery price and promotion watch: which milk, bread or braai-pack went on promotion, what it cost in the Eastern Cape against the Western Cape, what dropped.

No account, no API key, no login, no browser. One request returns up to 100 products.

Input

FieldNotes
searchTermsWhat to search for, one per line (milk, coca cola, basmati rice). Each term is a search of its own, and its rows carry it in search_term. Leave empty to read whole categories.
categoriesRestrict the search to categories, or list whole categories when there are no search terms. One per line: a top-level name (bakery, milk-dairy-and-eggs, liquor-store), a category code from a pnp.co.za address (milk-dairy-and-eggs-423144840, or a sub-category such as cheese44805192), or the address itself (https://www.pnp.co.za/c/bakery-423144840). With search terms, every term is searched inside every category.
onPromotionOnlyKeep only products Pick n Pay lists under its On Promotion filter. Default off.
sortrelevance (best match, the default), price-asc or price-desc. It is also the order rows are written in.
storeCodeThe Pick n Pay store whose prices are read, WC44 by default. Prices differ by store (see below). A code Pick n Pay does not know fails the run with an error.
maxItemsProducts written in total, across all searches (default 100, up to 10,000).
maxItemsPerSearchOptional limit for each search term or category, so one broad term cannot use the whole maxItems allowance.
proxyConfigurationApify proxy with automatic datacenter exits by default.

Example input:

{
"searchTerms": ["milk", "coca cola"],
"categories": ["milk-dairy-and-eggs"],
"onPromotionOnly": true,
"sort": "price-asc",
"storeCode": "WC44",
"maxItems": 100,
"maxItemsPerSearch": 50
}

The top-level categories on 2026-11-05: food-cupboard, personal-care-and-hygiene, liquor-store, chocolates-chips-and-snacks, outdoor-and-diy, beverages, household-and-cleaning, home-and-appliances, health-and-wellness, stationery, pet-care, milk-dairy-and-eggs, frozen-food, baby, coffee-tea-and-hot-drinks, electronics-and-office, toys-and-games, fresh-fruit-and-vegetables, bakery, deli-and-party, fresh-meat-poultry-and-seafood, ready-meals-and-desserts, flowers-and-plants, office-equipment.

Output

Prices are numbers in South African rand; currency is ZAR. One row is one selling unit: a six-pack case of milk and a single bottle of the same milk are two products with two codes. The same code is unique within one search.

FieldNotes
search_term, category, sort, position, store_codeWhat the row was found by: the term (null for a category listing), the category code the search was restricted to (null for none), the sort order, the place in that listing counting from 1, and the store whose prices these are. Relevance order drifts a little between requests.
code, base_code, unit_codePick n Pay's id of the selling unit (000000000000214534_EA), the part before the underscore (the same for every selling unit of one product), and the part after it: seen as EA (a single), CS (a case), CK (a pack) and KGM (sold by the kilogram).
name, url, image_urlThe name as Pick n Pay prints it, the product page, and the 400 x 400 photo (null when there is none).
supplierThe supplier Pick n Pay lists the product under (TIGER BRANDS, PICK N PAY for its own range). The seller of record, not always the brand on the label.
category_pathThe category segments of the product address, top level first (["Bakery", "Cakes & Treats", "Cakes"]).
currencyZAR.
priceThe shelf price for the selling unit, already net of a promotion applied to the price itself. For a product sold by weight (price_is_average is true) it is Pick n Pay's estimate for one pack.
was_price, savingsThe price before the markdown and the rand saving, when Pick n Pay prints them. Null when it does not, never 0.
on_promotionTrue when Pick n Pay marks the shelf price as a promotion price. It is what Pick n Pay says, not a comparison against price history. Many Smart Shopper offers leave the shelf price alone and show only in promotions.
price_is_averageTrue for products sold by weight, where price is an estimate for one pack.
unit_price, unit_price_perThe price per unit of measure when Pick n Pay prints one (79.99 per 1kg). Null for both when it prints none.
promotions, promotion_endsThe promotion messages attached to the product as Pick n Pay words them (Combo For R70.00, R17.49, Collectaboks: Buy 2 For R90.00), and the earliest end date among them (YYYY-MM-DD, UTC). Mostly Smart Shopper (loyalty) offers. Empty list and null when there are none.
badgesThe text badges on the product tile (SAVE, SMART SHOPPER, APP DEAL, Online Only). Empty when none.
in_stock, stock_statusWhether Pick n Pay reports the product in stock in this store, and its status word (inStock, outOfStock). Out-of-stock products are still listed, with their price.
rating, review_countThe average star rating (1 to 5) and the number of reviews. Rating is null for a product with no reviews (about 99% of rows: most products have none); review_count is then 0.
sponsoredTrue when the listing is a paid placement.
scraped_atWhen the row was read, UTC.

Example row (real values, 2026-11-05, the bakery category on promotion, store WC44):

{
"search_term": null,
"category": "bakery-423144840",
"sort": "relevance",
"position": 2,
"store_code": "WC44",
"code": "000000000000214534_EA",
"base_code": "000000000000214534",
"unit_code": "EA",
"name": "PnP Dessert Topped Buns 6 Pack",
"url": "https://www.pnp.co.za/All-Products/Bakery/Cakes-%26-Treats/Cakes/pnp-dessert-topped-buns-6-pack/p/000000000000214534_EA",
"image_url": "https://cdn-prd-02.pnp.co.za/sys-master/images/h04/hba/11470151876638/silo-product-image-v2-21Aug2024-180128-2005523000000-Straight_on-242177-1260_400Wx400H",
"supplier": "PICK N PAY",
"category_path": ["Bakery", "Cakes & Treats", "Cakes"],
"currency": "ZAR",
"price": 22.99,
"was_price": 25.99,
"savings": 3.0,
"on_promotion": true,
"price_is_average": false,
"unit_price": null,
"unit_price_per": null,
"promotions": [],
"promotion_ends": null,
"badges": ["SAVE"],
"in_stock": true,
"stock_status": "inStock",
"rating": null,
"review_count": 0,
"sponsored": false,
"scraped_at": "2026-11-04T20:11:43Z"
}

Honest nulls, and what the search does not carry

A field the source did not give is null, never 0 or "". promotions, badges and category_path are empty lists when there are none.

  • Prices are per store. The same product costs different amounts in different stores: 6 x 1L UHT milk was R97.99 in WC44 and R99.99 in EC01 on 2026-11-05. Many store codes do not exist (GP01, KZ01 and FS01 were refused); the run fails with an error naming the code rather than returning another store's prices.
  • The search is fuzzy. A term Pick n Pay has no product for still returns loosely related products (zzqqxx returned 16, cucumber, tomatoes and bananas first). A term inside a category that has none of it returns nothing (laptop in bakery), is reported in the log and costs nothing. Check search_term and name when a term is unusual.
  • A term does not contain :. It separates the filters in Pick n Pay's query, so inside a term it is read as a space.
  • Promotions are what the search shows. promotions and promotion_ends come from the offers attached to the product in the search result; they do not say whether a Smart Shopper card is needed. on_promotion follows the shelf price. The onPromotionOnly filter is Pick n Pay's own On Promotion filter, which also lists products whose only promotion is a Smart Shopper offer.
  • No brand, ingredients or nutrition. The search result carries the supplier and the product tile, not the product page's description, brand, ingredients or nutrition table. This actor does not read the product pages.
  • At most 10,000 products per search. Pick n Pay's search ends at 10,000 results (100 pages of 100); the log names a search that has more and says to narrow it by category or by the promotion filter.
  • A product is written once per search. A repeat the live order shows on a later page is not written or charged again. The same product found by two different search terms is written once for each, with its own search_term.

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.00105 per product written (product-scraped)

A row that is not written is not charged: a repeat of a product the same search already returned, and a search that fails, are free.

How it fetches

pnp.co.za is a storefront on SAP Commerce (Hybris) whose pages call its own API. This actor calls the same one: POST https://www.pnp.co.za/pnphybris/v2/pnp-spa/products/search with the search in the query string (query=milk:relevance, pageSize=100, currentPage=0, storeCode=WC44) and an Accept: application/json header. It asks for fields=products(FULL),pagination(DEFAULT), which is the products and the pager only: the default reply adds every filter facet of the search and is 298 KB for two products. A browser's own Accept header is answered with XML, so the header is sent explicitly. The same address fetched with GET is refused (HTTP 400: Pick n Pay reads search as a product code).

Things worth knowing, all measured on 2026-11-05:

  • Reachable from Apify. The POST was answered with JSON 10 of 10 times through Apify's datacenter proxy, 10 of 10 from the platform's own address and 10 of 10 through residential (a throwaway probe run on the Apify platform, a browser TLS fingerprint, the same request this actor makes). A refused request is retried from a fresh address.
  • Paging. 100 products per page at most (a larger pageSize is answered with 100) and the reply says how many results and pages there are. A request for a small maxItems asks for a small page. A page past the end is HTTP 200 with no products.
  • Categories. A term inside a category is milk:relevance:category:<code>; a whole category with no term is :relevance:allCategories:<code>. The two forms are not interchangeable on Pick n Pay's side, and this actor uses the right one for each. An unknown category code fails the run.
  • Sort and promotion filter. price-asc and price-desc reorder the whole result; the On Promotion filter narrowed milk from 1,249 to 304 products.
  • Weight. About 1.9 KB per product, so a 30-product run is one request and a second or two.

If Pick n Pay changes the API or its reply, the run fails with a message instead of returning empty rows.