# eMAG.ro Scraper — Products, Prices, Sellers & Reviews (`teodor_banea/emag-scraper`) Actor

Scrape eMAG.ro products from categories, searches, seller pages or product codes: price, discount, PRP, all seller offers, stock, EAN, specifications, images, rating breakdown and reviews. Exact limits, list-only fast mode, no proxy setup.

- **URL**: https://apify.com/teodor_banea/emag-scraper.md
- **Developed by:** [Banea Teodor](https://apify.com/teodor_banea) (community)
- **Security:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 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

## eMAG.ro Scraper — Products, Prices, Sellers & Reviews

### What does eMAG.ro Scraper do?

eMAG.ro Scraper collects data from **eMAG.ro**, Romania's largest online marketplace. Paste any eMAG page — a category, a brand page, a filter or price range, a sorted list, search results, a seller's shop or single product pages — or type search words or product codes, and pick one of three modes:

- **Listings** — one row per product straight from the list pages: price, struck-through price and discount, PRP, seller and seller type, stock, delivery, rating, review count, badges. The fastest way to read whole categories.
- **Products** — one full row per product: everything above plus brand, **EAN**, MPN, category path, all specifications (as columns or as a list), description as plain text, all gallery images, warranty, return period, campaigns, **every seller's offer**, rating breakdown and review summary, variants (colour, size, capacity…).
- **Reviews** — one row per review of each product: stars, title, text, date, verified purchase, helpful votes, which variant was reviewed — never reviewer names.

No login, no cookies, no proxy setup: the Actor handles access to eMAG by itself.

### Why use this Actor?

- **Exact limits** — the run stops at exactly the number of rows you ask for, never "about" that many.
- **Price monitoring built in** — enter up to 5,000 product codes per run and schedule it; every row carries the price, discount, PRP, 30-day low, buy-box seller and all competing offers.
- **Deep lists** — eMAG lets nobody page past item 6,000 of a list. The Actor cuts longer lists into price ranges automatically, so a 14,000-product search really returns ~14,000 products.
- **Correct barcodes** — the EAN comes from the seller's own barcode when it is a valid GTIN (eMAG's catalogue EAN is often another product's); `eans` lists every valid barcode seen.
- **No proxy settings to get wrong** — there is no proxy field; the Actor picks the connection that works and slows down by itself if eMAG asks for a security check.
- **Clean, stable rows** — every row of a run has the same keys (null when eMAG has no value), numbers are numbers, prices in RON, dates in ISO 8601, no HTML.

### How much does it cost?

The Actor is priced per row: **$1.20 per 1,000 rows** plus **$0.005 per run start**, in every mode. A row is a product (Listings, Products) or a review (Reviews). Platform usage is included.

| Example run | Rows | Price |
|---|---|---|
| 20 laptops with full details (the prefilled input) | 20 | $0.029 |
| 1,000 products of a category, Products mode | 1,000 | $1.205 |
| 1,000 products of a category, Listings mode | 1,000 | $1.205 |
| Daily price check of 500 product codes (30 runs a month) | 15,000 | $18.15 / month |
| All 644 reviews of one phone | 644 | $0.778 |
| A whole 14,000-product search, Listings mode | 14,000 | $16.805 |

Product codes that do not exist, pages that are not product lists, products without reviews and duplicates produce no rows and cost nothing beyond the start fee.

### Input

| Field | What it does | Example |
|---|---|---|
| **What to scrape** | Listings, Products or Reviews (see above). | `products` |
| **eMAG URLs** | Categories, brand pages (`/brands/brand/…`), filter / price / sorted lists, search results, seller pages, product and fashion pages. Lists are read from their first page. | `https://www.emag.ro/laptopuri/c` |
| **Search terms** | Words to search on eMAG, one per line. | `aspirator robot` |
| **Product codes** | eMAG product codes (the code after `/pd/` in a product URL) or product URLs, up to 5,000 per run. | `DZ4H93YBM` |
| **Sort lists by** | Order for lists without their own sort: relevance, price up / down, newest, most reviews, biggest discount. | `priceAsc` |
| **Minimum / Maximum price** | Keep only products in this price range (Lei). | `1000` / `3000` |
| **Maximum results** | Total rows per run (products, or reviews in Reviews mode); the run stops exactly there. 0 = no limit (capped at 20,000). | `100` |
| **Maximum results per URL or search term** | Products one source may contribute, so one big category does not use up the whole run. | `50` |
| **Include all seller offers** | Products mode: add every seller's offer to the row. | `true` |
| **Include description** | Products mode: add the description as plain text. | `true` |
| **Reviews per product** | Reviews mode: newest reviews read per product (0 = all, up to 10,000). | `100` |

Example input (Products mode):

```json
{
    "mode": "products",
    "startUrls": [{ "url": "https://www.emag.ro/laptopuri/brand/lenovo/c" }],
    "searchTerms": ["aspirator robot"],
    "productCodes": ["DZ4H93YBM"],
    "sort": "priceAsc",
    "maxResults": 200,
    "maxResultsPerSource": 100
}
```

Example input (Reviews mode — all reviews of two products):

```json
{
    "mode": "reviews",
    "productCodes": ["DZ4H93YBM", "DQWN0M2BM"],
    "maxReviewsPerProduct": 0,
    "maxResults": 2000
}
```

### Output

Every row of a run has the same keys; missing values are `null` (or `[]`), never left out.

**Products mode** — example (shortened lists; the seller is fictitious):

```json
{
    "pnk": "DZ4H93YBM",
    "productId": 60457121,
    "offerId": 137259074,
    "url": "https://www.emag.ro/telefon-mobil-apple-iphone-15-128gb-5g-black-mtp03rx-a/pd/DZ4H93YBM/",
    "title": "Telefon mobil Apple iPhone 15, 128GB, 5G, Black",
    "brand": "Apple",
    "brandId": 151,
    "mpn": "MTP03RX/A",
    "ean": "0195949035944",
    "eans": ["0195949035944"],
    "price": 3499.99,
    "currency": "RON",
    "originalPrice": 3999.99,
    "discountPercent": 13,
    "discountAmount": 500,
    "recommendedRetailPrice": null,
    "lowestPrice30Days": 3999.99,
    "priceNet": 2892.55,
    "pricePerUnit": null,
    "pricePerUnitLabel": null,
    "availabilityCode": "in_stock",
    "availabilityText": "În stoc",
    "inStock": true,
    "deliveryMethods": ["courier", "pickup"],
    "sellerName": "eMAG",
    "sellerId": 1,
    "sellerSlug": "emag",
    "sellerUrl": "https://www.emag.ro/emag/1/v",
    "isSoldByEmag": true,
    "isFulfilledByEmag": true,
    "vendorType": "emag",
    "isGeniusEligible": true,
    "hasFreeDelivery": false,
    "hasPickup": true,
    "itemCondition": "new",
    "hasVideo": false,
    "has360": false,
    "badges": ["Super Pret"],
    "campaigns": ["Campanie Produse Resigilate"],
    "warrantyMonths": 24,
    "warrantyBusinessMonths": 12,
    "returnPeriodDays": 30,
    "categoryId": 93,
    "category": "Telefoane Mobile",
    "categoryPath": ["Laptop, Tablete & Telefoane", "Telefoane mobile & accesorii", "Telefoane mobile"],
    "categoryUrl": "https://www.emag.ro/telefoane-mobile/c",
    "department": "Laptop, Tablete & Telefoane",
    "description": "Telefon mobil Apple iPhone 15, 128GB, 5G, Black\n\nDynamic Island...",
    "highlight": "Dynamic Island organizează eficient notificările și activitățile live",
    "highlightOverview": "Telefonul oferă o experiență fluidă cu ecran luminos, cameră performantă și autonomie extinsă.",
    "highlights": ["Camera principală de 48 MP cu zoom optic 2x și Night mode"],
    "specifications": { "Tip telefon": "Smartphone", "Capacitate stocare": "128 GB", "Culoare": "Negru" },
    "specificationGroups": [{ "group": "Caracteristici generale", "name": "Tip telefon", "value": "Smartphone" }],
    "imageUrl": "https://s13emagst.akamaized.net/products/60457/60457121/images/res_example.jpg",
    "images": ["https://s13emagst.akamaized.net/products/60457/60457121/images/res_example.jpg"],
    "rating": 4.69,
    "reviewCount": 644,
    "questionCount": 46,
    "ratingDistribution": { "1": 25, "2": 9, "3": 15, "4": 45, "5": 550 },
    "positiveRatingPercent": 93,
    "boughtCount": 528,
    "reviewSummary": "Recenziile sunt în majoritate foarte pozitive...",
    "ratingAspects": [{ "name": "Durata de viata baterie", "rating": 4.43 }],
    "offersCount": 5,
    "otherOffersCount": 4,
    "minOfferPrice": 3499.99,
    "maxOfferPrice": 4623.71,
    "offers": [{ "offerId": 900000002, "isMainOffer": false, "price": 3599.99, "originalPrice": null, "sellerName": "Example Phones SRL", "sellerId": 54321, "isSoldByEmag": false, "isFulfilledByEmag": false, "vendorType": "marketplace", "availabilityCode": "in_stock", "availabilityText": "În stoc", "inStock": true, "isGeniusEligible": false, "hasFreeDelivery": false, "warrantyMonths": 24 }],
    "usedOffersCount": 0,
    "usedMinPrice": null,
    "familyId": 3763868,
    "familyName": "Apple iPhone 15 [capacitate+culoare]",
    "variants": [{ "dimension": "Culoare", "label": "Albastru", "pnk": "DR4H93YBM", "url": "https://www.emag.ro/telefon-mobil-apple-iphone-15-128gb-5g-blue-mtp43rx-a/pd/DR4H93YBM/", "price": 3499.99, "isAvailable": true, "isCurrent": false }],
    "source": "category",
    "sourceUrl": "https://www.emag.ro/telefoane-mobile/c",
    "searchTerm": null,
    "position": 3,
    "detailsIncluded": true,
    "scrapedAt": "2026-10-28T10:00:00.000Z"
}
```

**Listings mode** rows have the same keys; brand, EAN, specifications, description, gallery, offers list, rating breakdown and variants are empty (`detailsIncluded: false`), and the category comes from the list. Fashion products in list rows link to eMAG's colour page (`…/fd/…`), in Products rows to the exact product page (`…/pd/…`).

**Reviews mode** — one row per review (fictitious example):

```json
{
    "pnk": "DZ4H93YBM",
    "productTitle": "Telefon mobil Apple iPhone 15, 128GB, 5G, Black",
    "productUrl": "https://www.emag.ro/telefon-mobil-apple-iphone-15-128gb-5g-black-mtp03rx-a/pd/DZ4H93YBM/",
    "reviewId": 900000001,
    "rating": 5,
    "title": "Example title",
    "text": "Example review text.",
    "date": "2026-09-24T18:16:41.000Z",
    "isVerifiedPurchase": true,
    "helpfulVotes": 2,
    "hasMedia": false,
    "variantPnk": "DR4H93YBM",
    "source": "product",
    "sourceUrl": "https://www.emag.ro/x/pd/DZ4H93YBM/",
    "scrapedAt": "2026-10-28T10:00:00.000Z"
}
```

The dataset has table views for each mode: **Overview**, **Prices & sellers**, **Offers** (every offer as columns), **Specifications** (every specification as its own column) and **Reviews**. Download as JSON, CSV, Excel or XML.

Not provided: a separate manufacturer field (eMAG shows the brand only), seller ratings (eMAG shows them only for the other sellers of a product) and favourite counts (always 0 on eMAG's public pages).

Problems — product codes eMAG does not know, pages that do not show a product list, URLs from other shops — are listed under the **ERRORS** key of the run's key-value store, with the URL or code you entered and a short reason. When a list gives noticeably fewer products than it shows, the final status says so ("read 6,946 of 14,270 listed — …").

### Tips and tricks

- **First run**: keep Maximum results low (the prefill is 20).
- **Whole categories**: lists read to the end are read newest first, because eMAG's "most relevant" order shifts while you page and would repeat some products and skip others. Lists longer than 6,000 products are cut into price ranges; rows from such lists have `position: null`.
- **Complete vs sample**: category, brand, filter and seller lists give every product eMAG lists there. Search results are eMAG's search — they include loosely related products; use a category or filter URL for a precise set.
- **Top N**: to get the cheapest or newest N products, set **Sort lists by** (or paste a sorted eMAG URL) and Maximum results per URL = N.
- **Filters**: open the category on eMAG, click the filters you want (brand, price, any specification), and paste the resulting URL.
- **Reviews mode order**: product codes and product URLs take their reviews first; list and search sources start after them and share the rest of Maximum results in input order.
- **Reviews** are read newest first, with no gaps. Each product family is read once per run (colours and sizes share their reviews). They are shared by all variants of a product family; `variantPnk` says which variant was reviewed. Reviews with stars and a title but no text have `text: null`.
- **Fashion pages** (`…/fd/…` URLs) are read as the colour (and size) the URL shows; sizes appear as eMAG shows them ("M INTL").
- **Big runs**: about 250–300 products per minute in Products mode; the default run timeout is 2 hours — raise it in Run options for runs above ~25,000 rows.
- Only **emag.ro** is supported (not emag.bg or emag.hu).

### Scheduling

For price and stock monitoring, put your product codes (or a sorted category URL) into a saved task and schedule it daily or hourly. Each run returns the current state; join runs on `pnk` and compare `price`, `inStock`, `sellerName` and `offersCount` over time. For new reviews, schedule Reviews mode with a small Reviews per product (newest first) and de-duplicate on `reviewId`.

### Personal data

Reviews come without reviewer names, ids or avatars. Seller names are business names shown on eMAG; some small sellers trade under a person's name — process the data in line with GDPR.

### Integrations

Connect the dataset to Google Sheets, Make, Zapier, Slack or your own code through the Apify API and webhooks: every run's rows are available as JSON, CSV or Excel right after the run (or while it runs). Use the "Prices & sellers" view for a ready-made price-monitoring sheet.

# Changelog

This Actor's version history is a separate document: https://apify.com/teodor_banea/emag-scraper/changelog.md

# Actor input Schema

## `mode` (type: `string`):

Choose one kind of row per run. Listings: one row per product straight from list pages (price, seller, stock, rating, badges) — fastest. Products: one full row per product (EAN, specifications, description, gallery, all seller offers, rating breakdown, variants). Reviews: one row per review of each product (stars, title, text, date, verified purchase — no reviewer names). Every row costs the same.

## `startUrls` (type: `array`):

Paste eMAG.ro pages: categories (https://www.emag.ro/laptopuri/c), brand pages (…/brands/brand/x), filter, price or sorted lists, search results (https://www.emag.ro/search/aspirator), seller pages, or single product and fashion pages (…/pd/DZ4H93YBM/, …/fd/…). Lists are always read from their first page. Listings and Products modes read the products of lists; Reviews mode reads the reviews of those products.

## `searchTerms` (type: `array`):

Enter words to search on eMAG.ro, one per line (e.g. "aspirator robot"). Each one is read like the site's search results.

## `productCodes` (type: `array`):

Enter eMAG product codes (the code after /pd/ in a product URL, e.g. "DZ4H93YBM") or full product URLs — for price and stock monitoring (Products mode) or to read their reviews (Reviews mode). Up to 5,000 per run. In Listings mode, product codes are read in full. In Reviews mode, product codes and product URLs take their reviews first; lists start after them.

## `sort` (type: `string`):

Choose the order for lists and searches whose URL has no sort of its own. Lists read to the end are read newest first so that no product is skipped.

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

Keep only products from this price up (lists and searches). Leave empty for no lower limit.

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

Keep only products up to this price (lists and searches). Leave empty for no upper limit.

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

Stop after this many rows in total — products in Listings / Products mode, reviews in Reviews mode. The run stops exactly there. 0 = no limit (capped at 20,000 per run).

## `maxResultsPerSource` (type: `integer`):

Limit how many products each URL or search term may contribute (in Reviews mode: how many of its products have their reviews read). Leave empty or 0 to use only Maximum results.

## `includeOffers` (type: `boolean`):

Products mode: add every seller's offer (price, stock, seller, fulfilled by eMAG, warranty) to each row. Offer counts and the lowest / highest offer price are filled either way.

## `includeDescription` (type: `boolean`):

Products mode: add the product description as plain text (paragraphs kept). Turn off for smaller rows.

## `maxReviewsPerProduct` (type: `integer`):

Reviews mode: read up to this many reviews of each product, newest first. 0 = all of them (up to 10,000 per product). Reviewer names are never collected.

## Actor input object example

```json
{
  "mode": "products",
  "startUrls": [
    {
      "url": "https://www.emag.ro/laptopuri/c"
    }
  ],
  "searchTerms": [
    "aspirator robot",
    "cafea boabe"
  ],
  "productCodes": [
    "DZ4H93YBM",
    "DQWN0M2BM"
  ],
  "sort": "relevance",
  "maxResults": 20,
  "includeOffers": true,
  "includeDescription": true,
  "maxReviewsPerProduct": 100
}
```

# Actor output Schema

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

Every row of the run as JSON.

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

Image, code, title, price, original price, discount, seller, stock, rating, reviews, URL.

## `prices` (type: `string`):

Price monitoring columns: price, discount, PRP, 30-day lowest price, seller, stock, offer counts.

## `offers` (type: `string`):

Every seller offer of each product, flattened into columns.

## `specs` (type: `string`):

Brand, EAN, MPN, category path and every specification as its own column.

## `reviews` (type: `string`):

Reviews mode: product, stars, title, text, date, verified purchase, helpful votes.

## `resultsCsv` (type: `string`):

The same rows as a CSV download.

## `errors` (type: `string`):

Products not found, pages that are not product lists and invalid inputs. Only present when something went wrong.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.emag.ro/laptopuri/c"
        }
    ],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("teodor_banea/emag-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 = {
    "startUrls": [{ "url": "https://www.emag.ro/laptopuri/c" }],
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("teodor_banea/emag-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 '{
  "startUrls": [
    {
      "url": "https://www.emag.ro/laptopuri/c"
    }
  ],
  "maxResults": 20
}' |
apify call teodor_banea/emag-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,teodor_banea/emag-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/1BpTfB8j9qroMr3Nf/builds/zKMleMsihtcc4h8VE/openapi.json
