# Disco Argentina Price, Promo & Stock Tracker (`automatic/disco-ar-scraper`) Actor

Product price search on Disco, a major Argentine supermarket. Search by keyword and get structured records: name, brand, price, list price, stock, unit data, price-per-kg and URLs.

- **URL**: https://apify.com/automatic/disco-ar-scraper.md
- **Developed by:** [Philip Kirkbride](https://apify.com/automatic) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Disco Argentina Price, Promo & Stock Tracker

Search Disco Argentina's online supermarket for structured product prices in ARS — groceries, drinks and household goods with brands, per-kg prices, stock and URLs.

🇦🇷 **Argentina** · storefront: `www.disco.com.ar` · prices in **ARS** (AR)

Every run returns normalized product records — name, brand, current and list price in ARS, per-kilogram price, stock level, product and image URLs — ready for datasets, spreadsheets or dashboards. Fast, structured results: no manual copy-paste.

### Use cases

- **Argentine grocery price monitoring** — track inflation-sensitive staples day by day.
- **Basket comparison** — pair with sibling Cencosud banners Jumbo and Vea; shared catalog makes SKUs comparable.
- **Inflation research** — high-frequency price series on AR-staples (yerba, aceite, fideos).
- **Assortment feeds** — structured fields for price pages and market analysis.

### What it returns

- `productKey` — stable unique product ID for this store; join and dedupe on this, never the bare `id`
- `name` / `brand` — product display name and brand
- `price` / `listPrice` / `currency` — current and list price; the currency for this store is always ARS
- `unitMultiplier` / `measurementUnit` — pack size (a 5 kg bag → multiplier 5, unit kg)
- `pricePerKg` — normalized per-kilogram price when the weight is known, `null` otherwise
- `availableQuantity` / `inStock` — stock count (99999 means effectively unlimited) and availability flag
- `url` / `imageUrl` — product page and product image
- `store` / `searchQuery` / `categoryResolved` — run provenance: store slug, the query used, and whether the optional category filter resolved

### Input

- `search` — free-text product search, e.g. `arroz`; defaults to a starter search when omitted
- `category` — optional; free-text category to scope the search against the store's own category tree (e.g. `huevos`)
- `maxPages` — 1–5 result pages (default 1; 24 products per page)
- `maxResults` — cap on returned products (default 50, max 200)

This record always searches `www.disco.com.ar` — no store selection is needed (or offered); every record of this family is fixed to one storefront.

### Example queries

{"store": "disco", "search": "arroz", "maxResults": 10}
{"store": "disco", "search": "yerba mate", "maxResults": 10}
{"store": "disco", "search": "aceite girasol", "maxResults": 5}
{"store": "disco", "search": "fideos", "maxResults": 20}

### Output example

```json
[
  {
    "productKey": "disco.com.ar:34075",
    "name": "Arroz Extra 5 kg",
    "brand": "Marca Blanca",
    "price": 26.9,
    "listPrice": 29.9,
    "currency": "ARS",
    "unitMultiplier": 5,
    "measurementUnit": "kg",
    "availableQuantity": 147,
    "pricePerKg": 5.38,
    "inStock": true,
    "url": "https://www.disco.com.ar/arroz-extra-5kg/p",
    "imageUrl": "https://www.disco.com.ar/arquivos/ids/34075/arroz-extra.jpg",
    "store": "disco",
    "searchQuery": "arroz",
    "categoryResolved": true
  }
]
```

### Quirks

1. AR price volatility is real — timestamp every record (`collectedAt`) and store history.
2. Disco/Jumbo/Vea share one Cencosud catalog — identical products at identical prices are ground truth, not duplication.
3. Large pack sizes are common; `pricePerKg` normalizes comparisons.

### Promo signals and price history

Unit prices are limited to grocery/consumer-goods bindings. `unitPriceSource` records `name`, `measurement`, or `none`; ambiguous sizes yield null. Non-grocery bindings return null, including appliance capacities.

Every product row now includes `skuSignals` for all returned SKUs: discount amount and percent against list price, promo/teaser names, installment plans, and each seller's stock flag. The displayed SKU also has these fields at the top level. `unitPrice` and `unitPriceBasis` normalize known weights, volumes and unit multipliers to kg, l or unit. Unknown measurements remain null; name-based pack estimates can be imperfect. A list-price discount does not guarantee checkout eligibility for a promotion or installment plan.

Set optional `historyStoreName` to a named key-value store in your own Apify account, then reuse that name and the same search/category/page/result limits. History starts with **your first run**, with no pre-existing price archive. Each SKU gets `previousPrice`, `priceChange`, `priceChangePercent`, `newSku`, `promoStarted`, `promoEnded`, `backInStock`, `outOfStock` and `daysSinceLastChange`. The displayed SKU's changes also appear on its product row. Days measure observed changes in price, promo status or stock, not the exact time a store changed them. First observations establish a baseline with `newSku: null`. Unit prices are null for clearly non-consumable catalog category paths (including appliances, electronics, furniture and clothing), even in mixed stores; ambiguous size/count names such as `Yogurt 1 kg 6 un` also remain null.

History retains at most 3,200 compact SKU observations in 16 chunks per search scope in your named store. Scopes never evict each other. Only active SKUs count toward the cap. Removed-SKU markers are retained for at most 30 days and 3,200 markers, evicting oldest first. Scopes exceeding 3,200 observed active SKUs report `historyStatus: "truncated"`, `newSku: null` (unknown), and suppress removals. When a complete scope fits again, one recovery pass reports `baseline` and `newSku: null`, then normal comparisons resume. Use a separate name for independent monitoring. Concurrent runs using the same store can overwrite each other's observations. Writes are best effort; interrupted writes can leave a partial baseline. Read/write failures preserve catalog rows and set `historyStatus` to unavailable. Bounded search results cannot prove catalog deletion: `removedSku` is false for observed SKUs. With history enabled, a SKU missing from a completed repeat of the same bounded query produces one `rowType: "historyRemoval"` event with `removedSku: true` and `removalReason: "notObservedInRepeatedSearchScope"`. This means disappearance from the monitored window, not verified catalog deletion. Current price and stock are null on these events; `previousPrice` is the last observation. Empty runs and unresolved categories report `incomplete-observation`, preserve SKU history without comparing or saving observations, and reset the smaller-observation streak. Resolved observations below 50% of the stored active scope also report `incomplete-observation`: seen SKUs are compared and saved, including price, promo and stock changes, while removals are withheld. After three consecutive non-empty smaller observations with resolved categories, the smaller scope is accepted and the run status explains this; up to 200 removals are emitted per run, with the remainder on later runs. Incomplete deadline-limited crawls skip history. Search ranking changes, page limits and outages can hide products. This is observation history, not a complete inventory audit.

For example, add `"historyStoreName": "my-store-price-history"` to one of the working search examples above. Keep the default dataset-item pricing: one product row or history-removal event is one charged item; nested SKU signals add no rows. `maxResults` caps product rows; history can add up to 200 disappearance events per run. No new custom charging events are used.

Set `includeCategorySummary: true` to add one `categorySummary` row per observed category: `count`, `pricedCount`, `medianPrice` and `promoShare` (0–1). These describe collected displayed-product offers, not the whole store; categories with unknown prices have a null median. Each summary row is one additional charged dataset item. History-removal events are excluded from summaries.

# Actor input Schema

## `search` (type: `string`):

Product search term (e.g. 'arroz'); defaults to a starter search when omitted. Sent as the storefront's full-text query. Exactly the inputs listed here are implemented; there is no detail-enrichment flag in v1.

## `category` (type: `string`):

Optional free-text category to scope the search (e.g. 'huevos'). Matched case-insensitively (accent-insensitive) against the store's own category tree; on a unique match the search is restricted to that category, which removes off-topic hits such as egg cutters for 'huevos'. If nothing or several categories match, the run falls back to the plain full-text search and every record carries categoryResolved=false.

## `maxPages` (type: `integer`):

Search-result pages to fetch (24 products per page).

## `maxResults` (type: `integer`):

Stop after this many products.

## `requestDelaySecs` (type: `number`):

Minimum 0.5 seconds.

## `historyStoreName` (type: `string`):

Starts history with your first run in your own account. Reuse the same name and search scope.

## `includeCategorySummary` (type: `boolean`):

Adds one charged dataset item per observed category: product count, median displayed price and promo share. Summaries cover collected rows only.

## Actor input object example

```json
{
  "search": "arroz",
  "maxPages": 1,
  "maxResults": 50,
  "requestDelaySecs": 1,
  "includeCategorySummary": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "search": "arroz"
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/disco-ar-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "search": "arroz" }

# Run the Actor and wait for it to finish
run = client.actor("automatic/disco-ar-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "search": "arroz"
}' |
apify call automatic/disco-ar-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automatic/disco-ar-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/fY60LuQWkjWTnmL5v/builds/y89cvQfrBgtL518R9/openapi.json
