# Magnit Grocery Price & Stock Monitor (`sashaebashu/magnit-monitor`) Actor

Scrape & monitor the Magnit (magnit.ru) grocery catalogue per store — price, discount (old price + %), rating and stock. Tracks promo start/end, price moves and products going out of stock between runs. Cheap datacenter proxy, no browser.

- **URL**: https://apify.com/sashaebashu/magnit-monitor.md
- **Developed by:** [Sasha Ebashu](https://apify.com/sashaebashu) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Magnit Grocery Price & Stock Monitor

Scrapes the **Magnit** grocery catalogue (magnit.ru) by category — product name, price, stock
status, image and URL — **for a specific store** — and tracks how prices and availability change
between runs.

Runs on Apify's cheap **datacenter** proxy. No browser, no residential proxy, no CAPTCHA solving.

### Prices are per STORE — this is the important part

Magnit does not have one national price list. Prices differ **per store**, not merely per city:
two Magnit shops on different streets of the same city can price the same item differently, and
across regions the gap is large. Measured on one category, comparing a Krasnodar store against a
Volzhsk store: **20 of the 47 products they share had different prices** — for example

| Product | Krasnodar | Volzhsk |
|---|---|---|
| Крабовые палочки Русское море охлажд. | 139.99 ₽ | 99.99 ₽ |
| Краб Крем-Фиш снежный 150г | 111.59 ₽ | 89.99 ₽ |
| Креветка Agama королевская | 569.99 ₽ | 599.99 ₽ |

So every row carries `shopCode` and `shopAddress`, and comparisons only make sense within one store.

#### Getting your store code

Open magnit.ru, pick your store, then read the **`shopCode`** cookie — that number is the input.
Leave **Store code** empty to use whatever store the site defaults to; the Actor still reports which
one that was.

> **An unknown code does not raise an error.** Magnit silently falls back to its default store and
> serves those prices instead. The Actor detects this and logs a clear warning, so a typo cannot
> quietly hand you the wrong store's data.

### What you get

One row per product:

`store`, `shopCode`, `shopAddress`, `productId`, `name`, **`price`**, **`oldPrice`**,
**`discountPercent`**, **`onSale`**, `currency`, `available`, `unitPrice`, `unitLabel`,
`isWeighted`, `cashback`, **`rating`**, `reviewsCount`, `ratingsCount`, `image`, `url`,
`categoryName`, `categoryPath`, `scrapedAt`

#### Discounts are the point

Magnit runs promos constantly — in a 1,200-product sample **61% of items were discounted**. Every
row carries the pre-promo `oldPrice` and the `discountPercent`, so you can tell a genuine shelf-price
change from a temporary promo. Ratings and review counts come along for free.

#### Monitor mode

Each row also gets `changeType`, `previousPrice`, `priceLow30`, `priceHigh30`, `firstSeenAt`,
`lastConfirmedAt`.

| `changeType` | Means |
|---|---|
| `new` | First time this product was seen in this scope |
| `promo_start` | Went on discount since the last run |
| `promo_end` | Discount finished |
| `price_up` / `price_down` | Shelf price moved with no promo change |
| `out_of_stock` | Was in the catalogue before and has now vanished from it |
| `unchanged` | Nothing moved |

`promo_start` / `promo_end` are checked before the plain price comparison, because a drop caused by
a promo is a different event from the shelf price itself changing — only the former reverts.

**About `out_of_stock`:** Magnit does not mark items as unavailable — it simply stops listing them
(the catalogue reports `InStock` for every product it shows, 99/99 sampled). So a product that was
in the baseline and is no longer in the listing has gone out of stock or been delisted. It is
reported **once**, then dropped from the snapshot. This detection is **skipped** whenever the run
did not cover every category in full — cut short by the time budget, the cost cap, `Max results`, or
a category that returned nothing — because then "missing" would just mean "not reached". The log
says so when that happens.

Turn on **Output only changed products** to drop `unchanged` rows — you then pay for and read only
what actually moved. `out_of_stock` rows are always emitted; a disappearance is a change.

### Input

| Field | What it does |
|---|---|
| **Category URLs** | Categories to scrape. Paste from the browser (`https://magnit.ru/catalog/4998-ryba_moreprodukty`) or just the `id-slug`. |
| **Store code (shopCode)** | Which store's prices to read. Empty = site default. |
| **Crawl all categories** | Discover categories from Magnit's sitemap (~714) instead of listing them. |
| **Category filter** | With the sitemap crawl: keep only categories whose URL contains this text (`moloko`, `ryba`, …). |
| **Max categories from sitemap** | Safety cap for the sitemap crawl. |
| **Max pages per category** | 32 products per page; the Actor stops on its own when a category ends. |
| **Max results** | Global cap. 0 = no limit. |
| **Monitor mode / Output only changed** | Change-tracking, described above. |

### Notes & limits

- **The listing is the data source.** Magnit's individual `/product/<id>` page is rendered in the
  browser and contains no price in its HTML, so this Actor reads the category listings, which Magnit
  server-renders into its Nuxt payload (with the JSON-LD `OfferCatalog` as a fallback if that payload
  ever changes shape — those rows then carry price and name but no discount, rating or unit data,
  and the log says how many pages were affected). Reading listings is also why it stays cheap:
  measured **$0.03 per 1,000 products**.
- **Monitor snapshots are scoped to the categories *and* the store.** Changing the store starts a
  fresh history rather than reporting every price as "changed" — which is what a shared snapshot
  would produce, since prices legitimately differ per store.
- The Actor honours your **Maximum cost per run**: it stops when the cap is reached and says so,
  instead of scraping pages the platform would discard.
- If a category yields nothing, it is reported in the log and saved to the `EMPTY_CATEGORIES` record
  rather than passing silently.

# Actor input Schema

## `categoryUrls` (type: `array`):

Magnit catalogue categories to scrape. Paste the URL from your browser (https://magnit.ru/catalog/4998-ryba\_moreprodukty), or just the id-slug part. Leave empty and turn on "Crawl all categories" to take the whole catalogue.

## `shopCode` (type: `string`):

IMPORTANT: Magnit prices differ per STORE, not just per city — two shops on different streets of the same city can price the same item differently. Leave empty to use the site's default store. To get your code: open magnit.ru, pick your store, then read the `shopCode` cookie. An unknown code does not error — the site silently serves its default store, so the Actor checks for that and warns you.

## `crawlSitemap` (type: `boolean`):

Discover categories from Magnit's own sitemap (~714 categories) instead of listing them by hand. Combine with the filter and limit below.

## `categoryFilter` (type: `string`):

Only used with "Crawl all categories". Keeps categories whose URL contains this text, e.g. `moloko`, `ryba`, `ovoshchi`.

## `maxCategoriesFromSitemap` (type: `integer`):

Safety cap for the sitemap crawl. 0 = no limit (the full catalogue is large — expect a long run).

## `maxPagesPerCategory` (type: `integer`):

Each page holds 32 products. The Actor stops earlier by itself when a category runs out of pages.

## `maxItems` (type: `integer`):

Stop after this many products in total. 0 = no limit.

## `maxConcurrency` (type: `integer`):

Parallel page requests.

## `monitorMode` (type: `boolean`):

Compare against the previous run for the SAME categories and store, and label every row: new / price\_up / price\_down / stock\_change / unchanged, plus previous price, 30-day low & high, and first-seen / last-confirmed stamps.

## `onlyChanges` (type: `boolean`):

Monitor mode only — drop the `unchanged` rows so you pay for and read only what moved. History is still updated for everything.

## `proxyConfiguration` (type: `object`):

Magnit works over Apify's cheap datacenter proxy — the default is fine.

## Actor input object example

```json
{
  "categoryUrls": [
    "https://magnit.ru/catalog/4998-ryba_moreprodukty"
  ],
  "crawlSitemap": false,
  "maxCategoriesFromSitemap": 50,
  "maxPagesPerCategory": 10,
  "maxItems": 0,
  "maxConcurrency": 6,
  "monitorMode": false,
  "onlyChanges": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `details` (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 = {
    "categoryUrls": [
        "https://magnit.ru/catalog/4998-ryba_moreprodukty"
    ],
    "shopCode": "",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("sashaebashu/magnit-monitor").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 = {
    "categoryUrls": ["https://magnit.ru/catalog/4998-ryba_moreprodukty"],
    "shopCode": "",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("sashaebashu/magnit-monitor").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "categoryUrls": [
    "https://magnit.ru/catalog/4998-ryba_moreprodukty"
  ],
  "shopCode": "",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call sashaebashu/magnit-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=sashaebashu/magnit-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/roJz1dvefnz6siume/builds/wfCXJqGVFp0jXEb6U/openapi.json
