# eMAG Scraper - Product Prices & Ratings (RO, BG, HU) (`s-r/emag-scraper`) Actor

eMAG scraper for search results and category listings in Romania, Bulgaria and Hungary. Per product: full title, numeric price in local currency, offer ID, rating, review count, category and stock wording. No seller account or API key. For price monitoring and assortment research.

- **URL**: https://apify.com/s-r/emag-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** E-commerce, Business
- **Features:** Limited permissions
- **Stats:** 7 total users, 5 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 run start fees

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 Scraper

This eMAG scraper turns a search term or category page from emag.ro, emag.bg or emag.hu into
product rows, for price monitoring and assortment research in Romania, Bulgaria and Hungary.
It returns products from eMAG search results and category listings. Each product is a dataset row with its full title, numeric price,
local currency, offer identifier, category, rating, review count and availability wording.
Use it to compare current listings, follow category prices or collect a repeatable product
snapshot. The actor reads listing cards; it does not open each product page for reviews,
seller details or technical specifications.

### eMAG Scraper input

Provide a search term in `query` or an eMAG search or category address in `url`. When both
are present, `url` determines the page to read and `query` remains on the output rows for
compatibility. Search terms use the selected market's language. Romanian searches use
`emag.ro`, Bulgarian searches use `emag.bg` and Hungarian searches use `emag.hu`.

| Field | Type | Default | Meaning |
|---|---|---|---|
| `query` | string | none | Search term; the form prefills `laptop` |
| `market` | string | `ro` | `ro`, `bg` or `hu` |
| `url` | string | none | eMAG search or category URL; takes precedence over query |
| `limit` | integer | `60` | Maximum products to return, from 1 to 1000 |
| `retries` | integer | `3` | Attempts per source page, from 1 to 6 |

The following input was used for a real run:

```json
{"query":"laptop","market":"ro","limit":60,"retries":3}
```

The search term has no default. Leaving both `query` and `url` empty produces a successful
empty run with a message explaining the missing input. The form's prefilled search term
is a convenience for the first run, not an implicit search performed when the field is
absent. Existing API calls keep the same parameter names and defaults.

An absolute URL on `emag.ro`, `emag.bg` or `emag.hu` selects its own market and currency.
This prevents a Bulgarian page from being reported with Romanian prices when `market`
was left at its default. Relative category paths use the selected market. Other hosts
are rejected. Category URLs can contain the filters selected on eMAG itself.

### eMAG Scraper output

Every product row retains the original twenty fields. `product_id` identifies the product
and `offer_id` identifies the offer shown on its listing card. Both are strings, even
when their contents consist entirely of digits. `position` numbers delivered products
from one after duplicates have been removed; `site_position` preserves the card position
on its source page and can restart on another page.

`title` contains the full title from the listing card's structured attributes rather
than the visually shortened text. `url` points to the product page. `price` is a number
in `currency`: RON for Romania, EUR for Bulgaria and HUF for Hungary. `price_text` preserves
the site's wording and spaces. The price parser treats dots as thousands separators and
commas as decimal separators. Do not parse `price_text` as an English decimal number.

Category information appears in `category`, `category_id` and `category_trail`. Rating
and review totals are separated into `rating` and `reviews_count`. `availability` holds
the source wording, while `availability_id` remains a string. `badges` contains distinct
short labels in source order or null when none were present. `image` is the listing image.
Unavailable card values remain null; the actor does not infer them from another product.

`market` and `query` preserve the context of the request. Two additional fields make the
rows easier to combine with other data: `country` is RO, BG or HU, and `fetched_at` is the
UTC time when the page was fetched. Existing field names and JSON types are retained.

Here is one actual row from a measured run:

```json
{
  "product_id": "114096973",
  "offer_id": "315762855",
  "url": "https://www.emag.ro/laptop-gaming-asus-rog-strix-g18-g814pp-cu-procesor-amd-ryzentm-9-8940hx-pana-la-5-3ghz-18-full-hd-wuxga-ips-144hz-32gb-ddr5-ram-1tb-ssd-nvidiar-geforce-rtxtm-5070-8gb-gddr7-no-os-eclipse-gray-g814pp-/pd/DJ08JW3BM/",
  "title": "Laptop Gaming ASUS ROG Strix G18 G814PP cu procesor AMD Ryzen™ 9 8940HX pana la 5.3GHz, 18'', Full HD+, WUXGA, IPS, 144Hz, 32GB DDR5 RAM, 1TB SSD, NVIDIA® GeForce RTX™ 5070 8GB GDDR7, No OS, Eclipse Gray",
  "price": 13999.99,
  "price_text": "13.999 , 99 Lei",
  "currency": "RON",
  "category": "Laptop / Notebook",
  "category_id": "91",
  "category_trail": "Laptop, Tablete & Telefoane/Laptopuri si accesorii/Laptop / Notebook",
  "rating": 5.0,
  "reviews_count": 2,
  "availability": null,
  "availability_id": "2",
  "badges": [
    "Electro Weekend",
    "Easy BuyBack 200 lei BONUS",
    "ASUS Perfect Warranty"
  ],
  "image": "https://s13emagst.akamaized.net/products/100742/100741583/images/res_6ff3851613e1b38263c12394578067a8.png?width=720&height=720&hash=C37C1E39AFC9F6DA30DCFF2CEEBC3993",
  "site_position": 1,
  "fetched_at": "2026-10-04T07:14:13.702Z",
  "country": "RO",
  "market": "ro",
  "query": "laptop",
  "position": 1
}
```

The default key-value store also keeps a `summary` with delivered-product counts, page
counts, priced products, rated products and market information. Source errors are recorded
in `errors`. These records help distinguish an empty input, a missing category and an
incomplete collection. The dataset itself contains only product rows.

### Use cases

- **Price monitoring.** Schedule the same search or category URL daily or weekly and compare
  `price` per `product_id` between runs. Because each row carries `fetched_at`, the runs
  together form your own eMAG price history for the products you follow.
- **Assortment and category research.** Read a whole category, filtered the way you would
  filter it on eMAG, and see which brands, price points and availability levels it contains.
- **Competitor and brand tracking.** Search for a brand or model name and check where its
  products appear in `site_position`, what they cost and how many reviews they have collected.
- **Cross-market comparison.** Run the same term on `ro`, `bg` and `hu` to compare how one
  product line is priced in RON, EUR and HUF across the three eMAG sites.
- **Feeding a spreadsheet or BI tool.** Export the dataset as JSON, CSV or Excel from Apify, or
  read it through the Apify API from your own scripts and integrations.

### eMAG Scraper compared with other options

**The eMAG Marketplace API** is meant for sellers on eMAG. It manages a seller's own offers,
orders and stock and requires a seller account. It does not return the public search results
or category listings that shoppers see, with competing offers, ratings and review counts. This
actor reads exactly those public listing pages, without a seller account or API key.

**Copying listings by hand or with a browser extension** works for a single page. It becomes
slow when you follow several categories, three countries or more than a few dozen products,
and the numbers end up as text. Here the price is already a number, the currency is explicit
and identifiers stay stable between runs, so results can be compared automatically.

**Writing your own scraper** gives full control, but you then maintain the parsing of three
country sites, price formats with dots and commas, pagination and duplicate removal yourself.
This actor keeps one stable set of fields for all three markets; apart from a small start fee
you pay only for the products it delivers.

**Other eMAG actors in the Apify Store** differ in scope. Some cover only emag.ro, others also
open product pages for specifications. This actor focuses on the listing pages of all three
countries, with a fixed price per delivered row and the same output fields in every market.

### Pricing

The existing pay-per-event price remains **$0.0005 per run** plus **$0.0007 per delivered
product**. The start fee also applies to empty runs. A run returning no products has zero
product charges. A sixty-product run therefore costs $0.0425 for these actor events.
Apify platform charges, if applicable to your account, are separate from these prices.

Runs from users without a paid Apify plan receive at most ten product rows. Product
charges follow the rows actually delivered within that cap. Paying users retain their
requested limit, up to 1000. Set the maximum total charge in Apify when you need an
additional spending limit; delivery and actual event counts are reported in `OUTPUT`.

### Pagination and empty results

The actor follows source pages automatically for larger limits and removes duplicates by
`product_id`. Customers using the Apify actor do not need to manage continuation tokens.
A missing category, a listing with no cards or an inaccessible source returns a successful
empty run with an English error message. If a later page fails, products already found
are retained and the source error is reported.

Search results follow eMAG's own search behavior. eMAG can return fuzzy matches for an
unusual term, so a term that appears meaningless does not necessarily produce an empty
dataset. The actor delivers those source cards without applying its own relevance filter.
This preserves the behavior of existing queries and keeps the result set aligned with
what the listing page actually contains.

### FAQ

#### Does this work in all three countries?

Yes. Select `ro`, `bg` or `hu`, or supply an absolute URL from the corresponding eMAG site.
Currency and country follow the page's market. Amounts are not converted between currencies.

#### Can I scrape a filtered category instead of searching?

Yes. Put the category URL in `url`. It takes precedence over `query`, and the actor follows
the category's subsequent pages automatically up to the requested product limit.

#### Does this return product details or all sellers?

It returns the fields available on the listing cards. It does not promise every seller,
all product images, individual reviews, GTINs or specifications from the product page.
The offer identifier describes the offer represented by that card.

#### Why did I receive fewer products than the limit?

The source may have fewer distinct cards, repeat a page, or fail on a later page. Free
users are limited to ten delivered rows. Check `summary`, `errors` and `OUTPUT` to see
what the run delivered and whether any source page remained incomplete.

#### What happens when my search has no hits on eMAG?

The run still succeeds with an empty dataset, and `errors` explains why. When eMAG serves
the page without any product cards, for example a search page beyond the last result
page, the message reads: "eMAG served the page but it carried no product cards; the term
may have no hits." Such a run costs only the run start fee, never a product charge.

#### Is there an official eMAG API for product prices?

eMAG offers a Marketplace API for its sellers. It covers a seller's own catalog and orders,
not the public search results. To collect public prices, ratings and availability across
eMAG listings, use this scraper.

#### Can I build an eMAG price history with this actor?

Yes. Schedule a run on the same search term or category URL, keep the datasets, and join
the rows on `product_id` or `offer_id`. Each row has a `fetched_at` timestamp, so every run
adds one data point per product.

#### In which formats can I download the data?

Apify stores the rows in a dataset that you can download as JSON, CSV, Excel, XML or HTML,
or fetch through the Apify API and integrations.

# Actor input Schema

## `query` (type: `string`):

What to search for. Use the market's own language for the best results. Optional if you pass a URL instead.

## `market` (type: `string`):

eMAG market. An absolute eMAG URL determines the market and currency.

## `url` (type: `string`):

An eMAG search or category URL to read instead of a term. Paste it from your browser after applying any filters. Takes precedence.

## `limit` (type: `integer`):

How many products to return, 1 to 1000. A page carries about 60.

## `retries` (type: `integer`):

Retry attempts per page, within the shared request budget. Hungary uses up to six attempts within the same budget.

## `cursor` (type: `string`):

Opaque next_cursor from the previous response. Keep all original input values.

## Actor input object example

```json
{
  "query": "laptop",
  "market": "ro",
  "limit": 60,
  "retries": 3
}
```

# Actor output Schema

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

One row per product from an eMAG search or category page.

## `summary` (type: `string`):

Products returned, pages fetched, price and rating coverage, the market and its currency.

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

Per-page failures with a code and a redacted message.

# 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 = {
    "query": "laptop"
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/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 = { "query": "laptop" }

# Run the Actor and wait for it to finish
run = client.actor("s-r/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 '{
  "query": "laptop"
}' |
apify call s-r/emag-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/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/nhwThMeIQzIVVxTFL/builds/gdiqggjIVnpISrhIr/openapi.json
