# M.Video Scraper - Russian Electronics Prices by Category & City (`superslowsloth/mvideo-products`) Actor

mvideo.ru scraper: category ids (205 = smartphones) in Moscow, St Petersburg or 8 more cities to one row per product with shown, base and promo price, discounts, M.Club bonus rubles, stock flags, rating, seller, specs and photos. For Russian electronics price watches.

- **URL**: https://apify.com/superslowsloth/mvideo-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (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 $0.70 / 1,000 product scrapeds

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

## M.Video scraper: Russian electronics prices by category and city

This M.Video scraper reads [mvideo.ru](https://www.mvideo.ru)'s category
listings for the city you choose (Moscow, Saint Petersburg, Yekaterinburg,
Novosibirsk, Kazan and five more) and returns one flat row per product: the
**shown price in rubles**, the base (crossed-out) price, the promo price, the
discount and the promotions behind it, **M.Club bonus rubles**, stock flags
(sold out, retail-store only, pre-order), star rating and review count, the
**marketplace seller** when a third party sells it, brand and model, five key
specs and the photos. Run it on a schedule and diff the dataset to get a **price
watch** for a category in a city: which phones dropped in price, which went out
of stock, which marketplace sellers undercut M.Video.

No account, no API key, no login, no browser. One page is 48 products.

### Input

| Field | Notes |
|---|---|
| `categoryIds` | mvideo.ru category ids, one per line: the number at the end of a category link, so `https://www.mvideo.ru/smartfony-i-svyaz-10/smartfony-205` is `205` (smartphones, the default). The whole link works too. Up to 20 categories. |
| `city` | One of `moscow` (default), `saint-petersburg`, `yekaterinburg`, `novosibirsk`, `kazan`, `nizhny-novgorod`, `samara`, `krasnodar`, `chelyabinsk`, `rostov-on-don`. Prices and the assortment differ by city. |
| `maxItems` | Products to return per category (default 100, up to 20,000), cheapest first. |
| `proxyConfiguration` | Apify datacenter proxy by default. |

Example input:

```json
{
  "categoryIds": ["205"],
  "city": "moscow",
  "maxItems": 500
}
```

### Output

| Field | Notes |
|---|---|
| `city`, `city_name` | The city slug you asked for and its name as the site writes it (`Москва`). |
| `search_category_id` | The category id the product was listed under. |
| `position` | Place in the category in the site's price-ascending order, 1 = cheapest. The site sorts by its own index, which can trail a live price change: 3 of 96 neighbours checked were out of order by `price`, by 43 to 380 rubles. |
| `product_id` | M.Video's product id, **the unique key**. A product listed under two of your categories is returned once, under the first. |
| `name`, `url` | As shown, and `https://www.mvideo.ru/products/<product_id>`. |
| `brand`, `brand_id`, `model_name` | From the product card. |
| `category_id`, `category_name` | The product's own category (`Смартфоны`, `iPhone`). Its id is a different number from the listing's category id (products listed under 205 carry 761). |
| `price` | The price the site shows (`salePrice`), in rubles. |
| `base_price` | The price before discounts (`basePrice`). |
| `promo_price` | `basePromoPrice` as the site sends it. It equalled `price` on 98 of 100 products checked and was null (not sent) on the other 2. |
| `discount_amount`, `promotions` | The listed discounts added up, in rubles, and their names (`Скидка по акции`). `discount_amount` is 0 when the site lists no discount. |
| `is_final_price` | The site's own flag. |
| `bonus_rubles` | M.Club bonus rubles the purchase earns. |
| `currency` | Always `RUB`. |
| `rating`, `review_count` | Star rating out of 5 and the number of reviews. |
| `sold_out`, `available_only_in_retail_store`, `is_preorder` | Stock flags. A sold-out product still has a price, and is returned (35 of 108 products checked were sold out). |
| `used_product_type` | The site's used/refurbished marker (`BU1P`); null for new. |
| `seller_id`, `seller_name` | The marketplace seller for a product a third party sells. |
| `image_url`, `images` | The main photo and all photos (`https://img.mvideo.ru/...`). |
| `key_specs` | Up to five key specs as the site labels them, e.g. `{"Экран": "6.9\"/1600x720 Пикс", "Оперативная и встроенная память": "8 ГБ/256 ГБ"}`. |

#### Honest nulls

A field the site did not send is **null, never 0**:

- **`rating` is null for an unreviewed product.** The site sends a 0-star
  rating with 0 reviews there; that is not a rating of 0, so `rating` is null
  and `review_count` is 0. When the site sends neither, both are null.
- **`seller_id`/`seller_name` are null when the site names no supplier.** In
  the products checked that was every product from M.Video's own stock and
  none of the marketplace ones.
- **`promo_price` is null when the site does not send it**, as for 2
  of 100 products checked (both undiscounted). `bonus_rubles` and
  `base_price` are null the same way if a price record ever arrives without
  them; every one checked had them.
- **A product without a price is not returned.** Each row joins the product
  card and the city's price; an id the search lists but for which the card or
  the price does not come back is skipped, logged and not charged.

### 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.0007** per product row written (`product-scraped`), i.e. $0.70 per 1,000
  products. A row that is not written is not charged: repeats and products
  without a price are free.

### How it fetches

It reads the same JSON API (`/bff/...`) the mvideo.ru web app calls for a
listing page, three requests per 48 products: the category search (product ids
only), the product cards (`POST /bff/product-details/list`) and the city's
prices (`/bff/products/prices`). The city is four cookies the site sets when you
pick a city; their values for each supported city were read from the site's own
location endpoints (Moscow's match what the site's own pages send). Measured
2026-10-23 from Apify datacenter addresses: HTTP 200 on the search and prices
calls, and on the product-card call when a mvideo.ru Referer is sent (8 of 8;
0 of 10 without). A refusal (403 block page, 429, 5xx), an HTTP 200 without the
data, or the site's per-address escalation (an HTTP 400 asking for session
cookies that it sends after a few hundred requests from one address) is treated
as the address being refused: the actor rotates to a fresh address and retries.
It also changes address every 40 pages on its own.

Things worth knowing, all measured:

- **Cheapest first, always.** In the site's default order the search pages
  skip and repeat products (each page after the first returned 23 of 24, and
  two pages of 24 did not match one page of 48). Sorted by price ascending
  they page cleanly, so that is the order used.
- **How deep it goes: the whole category.** One walk of smartphones in Moscow
  listed 8,212 products, 8,212 distinct, matching the site's total; the last
  page held 12 and the next one none. The site's total moved between 8,212 and
  8,214 during the day, so the walk ends on a short or empty page, not on the
  total.
- **Unknown category ids** return no products (HTTP 200, total 0); the run
  logs it and goes on.
- **Other cities.** Only the ten cities above are offered, because each needs
  its own four cookie values and these ten were read from the site and
  checked. The category totals differ per city (smartphones: Moscow 8,213,
  Saint Petersburg 7,373, Novosibirsk 7,246 in the same hour).

If mvideo.ru changes its API, the run fails with a message instead of
returning empty rows.

# Actor input Schema

## `categoryIds` (type: `array`):

mvideo.ru category ids, one per line: the number at the end of a category link (https://www.mvideo.ru/smartfony-i-svyaz-10/smartfony-205 is 205, smartphones). A whole category link is accepted too. Up to 20 categories; a product listed in two of them is returned once, under the first. A category with no products in the chosen city returns nothing and is not a failure.

## `city` (type: `string`):

Prices and the assortment differ by city (smartphones listed 8,213 in Moscow, 7,373 in St Petersburg and 7,246 in Novosibirsk the same hour). The city's cookies were read from mvideo.ru's own location endpoints when this actor was built.

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

Return at most this many products from each category, cheapest first (the only order in which mvideo.ru's search pages through a category without skipping or repeating products). Smartphones in Moscow listed 8,212 products, so 10,000 reads that whole category; up to 20,000 are accepted.

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

Use Apify's automatic datacenter proxy (the default). mvideo.ru answered Apify datacenter addresses with HTTP 200 when this actor was built; a refused request is retried from a fresh address, and the address is changed every 40 pages on its own.

## Actor input object example

```json
{
  "categoryIds": [
    "205"
  ],
  "city": "moscow",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (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 = {
    "categoryIds": [
        "205"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/mvideo-products").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 = {
    "categoryIds": ["205"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/mvideo-products").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 '{
  "categoryIds": [
    "205"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/mvideo-products --silent --output-dataset

```

## MCP server setup

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

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/tCIl1qnjjteKleN49/builds/ALbJFrETBxPtzV4w6/openapi.json
