# Podrygka.ru scraper (`sashaebashu/podrygka-monitor`) Actor

Read the Podrygka (Подружка) beauty catalogue: article, brand, price, stock, rating and review count for every product, with optional discounts — plus alerts when a price moves, an item returns to stock or drops out of the catalogue.

- **URL**: https://apify.com/sashaebashu/podrygka-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/actors/running/actors-in-store.md#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

## Podrygka Price & Stock Monitor — Подружка Catalogue

Podrygka (Подружка) is one of Russia's largest beauty chains. This Actor reads its catalogue —
article number, name, brand, price, stock, rating and review count for every product — and tells you
when a price moves, an item comes back into stock, or an article drops out of the range entirely.

No key, no login, and no proxy needed.

### Where the data actually is

The catalogue is a Next.js app-router page, and its prices are **not in the markup**: the rendered
HTML carries no price attribute at all. The product data arrives inside the page's streamed data
chunks as schema.org objects, which is where this Actor reads it. That is why one category page
yields twenty complete products — article, brand, price, availability, rating, review count, images
and description — without opening a single product page.

The difference matters for cost. A product page on this shop weighs **two megabytes**; the listing
that describes twenty of them weighs one.

**Measured: 1,000 products in 45 seconds** across 16 categories and 160 brands.

### What you get per product

| | |
|---|---|
| **Identity** | Article number (`sku`), name, brand, link, category |
| **Price** | Current price and currency; optionally the old price and the discount |
| **Stock** | In stock, out of stock, or genuinely unknown — the three are kept apart |
| **Reputation** | Rating and the number of reviews behind it |
| **Media** | First image and how many the product has |

### Monitor mode

Run it on a schedule and get only what moved.

| `changeType` | Means |
|---|---|
| **`price_drop`** / **`price_rise`** | The price moved, with the old figure, the difference and the percentage |
| **`back_in_stock`** | It was out of stock last run and is available now |
| `out_of_stock` | The reverse |
| `new_reviews` | Customers reviewed it since the last run, with how many |
| **`no_longer_listed`** | The article was in the catalogue last run and is not in this one |
| `new`, `unchanged` | First sighting / nothing moved |

**`no_longer_listed` is only raised after a run that could actually have found the product.** Three
conditions must hold together: whole-catalogue mode is on, the run covered all 249 categories the
shop lists, and nothing was cut short — no failed page, no time limit, no product or cost cap. When
any of them fails the Actor says so in the log and raises nothing, because a delisting claim from a
partial run is a guess. An earlier version checked only that the flag was switched on, and promptly
declared fifteen perfectly ordinary products delisted after reading 3 categories out of 249.

### Notes & limits

Properties of the source, measured rather than assumed:

- **Pagination is `?PAGEN_1=N`.** `?page=`, `?p=` and `/page-2/` are all accepted by the server and
  all silently return page one — a scraper trusting them would report the same twenty products
  forever. Past the last page the shop repeats the previous one rather than returning nothing, so
  the walk ends when a page brings no new article numbers.
- **Every product is published twice in the page data.** Counting without de-duplicating makes a
  category look twice as full as it is.
- **A failed page is not the end of a category.** It stops the walk, marks the category incomplete,
  and blocks the delisting alert for that run.
- **The old price is not in the listing.** It exists only on the product page, so discounts are off
  by default; switching them on costs one two-megabyte page per product.
- **A product page carries several price pairs, not one.** One measured page held nine, belonging to
  two different products because the recommendations block brings its own. Pairing this product's
  price with a neighbour's old price produced a 48% discount that did not exist, so only the pair
  whose current price matches the catalogue price is used — and when no pair matches, nothing is
  reported rather than guessed.
- **Availability is read from the shop's own schema.org term.** Anything that is neither `InStock`
  nor `OutOfStock` is reported as unknown, not as "out of stock".
- **No proxy needed, and it costs time.** The same ten-page walk took 27 seconds direct and 143
  through a proxy, and the shop serves Apify's machines without complaint. The proxy stays available
  for long catalogue runs; if the shop starts asking the run to slow down, the log says so.
- The Actor honours your **Maximum cost per run** and stops when the cap is reached.

### Input

| Field | What it does |
|---|---|
| **Categories** | Category pages to read. Leave empty to take them from the shop's own sitemap. |
| **Scan the whole catalogue / Maximum categories** | Read every category the shop lists — required for the delisting alert. |
| **Pages per category** | Twenty products per page, about a megabyte each. The main cost dial. |
| **Brands / Minimum price / Maximum price / In stock only** | Filters, applied before anything is written. |
| **Include the old price and discount** | One product page per product. For a shortlist, not a catalogue. |
| **Monitor mode / Output only changes** | The alert behaviour above. |

# Actor input Schema

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

Category pages to read, for example `https://www.podrygka.ru/catalog/category_parfumeriya/`. Leave empty to take them from the shop's own catalogue sitemap.

## `scanWholeCatalogue` (type: `boolean`):

Read every category the shop lists in its sitemap — 249 of them, holding around 19,000 articles. The "no longer listed" alert is raised only after a run that actually covered all of them, so this must be on AND "Maximum categories" must be at least as large as the shop's own count.

## `maxCategories` (type: `integer`):

How many categories to take from the sitemap.

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

Each page holds 20 products and weighs about a megabyte, so this is the main cost dial.

## `brandFilter` (type: `array`):

Keep only these brands, spelled as the shop spells them — `LOREAL`, `MISSHA`. Leave empty for all.

## `minPrice` (type: `integer`):

Keep only products at or above this price. 0 turns it off.

## `maxPrice` (type: `integer`):

Keep only products at or below this price. 0 turns it off.

## `inStockOnly` (type: `boolean`):

Drop products the shop marks as out of stock. Products whose availability it does not state are kept, not guessed at.

## `includeDiscounts` (type: `boolean`):

The listing carries the current price but never the old one, so the discount can only be read from each product's own page — two megabytes apiece. Off by default; switch it on for a shortlist, not for a whole-catalogue run.

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

Stop after this many products.

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

4 is comfortable for megabyte pages.

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

Compare against the last run and label what moved: a price up or down with the old figure, a product coming back into stock or leaving it, new reviews, or an article no longer listed.

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

With monitor mode on, skip products where nothing moved — an empty run then means nothing needs your attention.

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

Off by default, and on measurement rather than caution: the same ten-page walk took 27 seconds direct and 143 through the proxy, and the shop serves Apify's machines without complaint. Switch it on for a whole-catalogue run, or if the shop starts asking the run to slow down.

## Actor input object example

```json
{
  "categoryUrls": [
    "https://www.podrygka.ru/catalog/makiyazh/litso/tonalnye_sredstva/",
    "https://www.podrygka.ru/catalog/category_parfumeriya/"
  ],
  "scanWholeCatalogue": false,
  "maxCategories": 250,
  "maxPagesPerCategory": 25,
  "brandFilter": [],
  "minPrice": 0,
  "maxPrice": 0,
  "inStockOnly": false,
  "includeDiscounts": false,
  "maxItems": 1000,
  "maxConcurrency": 4,
  "monitorMode": false,
  "onlyChanges": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `alerts` (type: `string`):

No description

## `full` (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://www.podrygka.ru/catalog/makiyazh/litso/tonalnye_sredstva/",
        "https://www.podrygka.ru/catalog/category_parfumeriya/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sashaebashu/podrygka-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://www.podrygka.ru/catalog/makiyazh/litso/tonalnye_sredstva/",
        "https://www.podrygka.ru/catalog/category_parfumeriya/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("sashaebashu/podrygka-monitor").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 '{
  "categoryUrls": [
    "https://www.podrygka.ru/catalog/makiyazh/litso/tonalnye_sredstva/",
    "https://www.podrygka.ru/catalog/category_parfumeriya/"
  ]
}' |
apify call sashaebashu/podrygka-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sashaebashu/podrygka-monitor"
        }
    }
}

```

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/Db5TEaxYotZ99virv/builds/MEV4D3h2jaI6naNha/openapi.json
