# eMAG Scraper (`s-r/emag-scraper`) Actor

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

## Pricing

Pay per event

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

## eMAG Scraper

Search eMAG and get the results back as rows: full product title, price in the market's own currency, rating, review count, stock wording, and the category eMAG files it under. Works on **Romania, Bulgaria and Hungary**.

No login, no cookie, no API key. It reads the ordinary listing page from an exit inside the market's own country, so the prices are the ones local shoppers see.

### What you get

- **Three markets from one actor.** `emag.ro`, `emag.bg` and `emag.hu` run on the same platform, and each is fetched through an exit in its own country with its own currency: RON, EUR and HUF
- **The complete product title**, read from the card's own data attribute instead of the visible text, which eMAG truncates with an ellipsis. On eMAG a title carries the CPU, the RAM, the SSD and the screen, so the truncated version loses the specification
- **`offer_id` next to `product_id`.** The product id is stable; the offer id is the specific seller's offer currently winning the listing. When that changes, someone else won the sale. No other eMAG actor returns it
- **Category, category id and the full category trail** on every row, so a run can be pivoted by category without a second lookup
- **Rating and review count** where the product has reviews, parsed apart rather than left as one string
- **Stock wording plus eMAG's own numeric availability code**, so you can filter on either the words or the code
- **Prices parsed for three locales.** The dot is a thousands separator in all of them, so `4.719,00 Lei` is 4719 and the Hungarian `66.412 Ft` is 66412 with no decimals at all

### Why eMAG

eMAG is not "a Romanian shop", it is the marketplace that most of Romanian e-commerce runs through, and it operates Bulgaria and Hungary as well. For anyone selling into those markets it is the price reference, the way Amazon is in Germany or bol.com is in the Netherlands. If you want to know what something costs in Bucharest, this is the answer.

Because it is a marketplace rather than a single retailer, a listing has a winning seller, and that is what `offer_id` exposes. A product whose `offer_id` changed between two runs is a listing that changed hands, usually because somebody repriced. That signal is invisible in a plain price scrape and it is the one that tells you the market moved rather than a single shop having a sale.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `query` | string | one of the two | `laptop` | What to search for. Use the market's own language |
| `market` | select | no | `ro` | `ro`, `bg` or `hu`. Sets the site, exit country and currency |
| `url` | string | one of the two | – | An eMAG search or category URL. Takes precedence |
| `limit` | integer | no | `60` | Products to return, 1 to 1000. A page carries about 60 |
| `retries` | integer | no | `3` | Retry attempts per page |

### Output

```json
{
  "position": 1,
  "product_id": "81659936",
  "offer_id": "220966421",
  "url": "https://www.emag.ro/laptop-lenovo-v15-g4-abp/pd/...",
  "title": "Laptop Lenovo V15 G4 ABP cu procesor AMD Ryzen 7 7730U pana la 4.5GHz, 15.6\" Full HD, IPS, 16GB DDR4, 512GB SSD, No OS, Business Black",
  "price": 4719.0,
  "price_text": "4.719 , 00 Lei",
  "currency": "RON",
  "category": "Laptop / Notebook",
  "category_id": "91",
  "category_trail": "Laptop, Tablete & Telefoane/Laptopuri",
  "rating": 5.0,
  "reviews_count": 11,
  "availability": "în stoc",
  "availability_id": "3",
  "badges": [
    "Top Favorite"
  ],
  "image": "https://s13emagst.akamaized.net/...",
  "site_position": 1,
  "market": "ro",
  "query": "laptop"
}
```

### Use cases

**Pricing into Romania, Bulgaria or Hungary.** Run the same product term in all three markets and you have the local price in the local currency, from a local IP, without maintaining three scrapers. The gap between them is usually larger than people expect.

**Watching who wins a listing.** Schedule a category and diff `offer_id` per `product_id`. A changed offer id is a seller change on that listing, and the accompanying `price` tells you what it cost them to take it.

**Category-level price tracking.** `category_id` and `category_trail` are on every row, so a single broad run can be grouped into category medians rather than needing one run per category.

**Finding what is actually in stock.** `availability` and `availability_id` separate live listings from placeholders, which matters on a marketplace where discontinued products keep their pages.

**Review mining targets.** `reviews_count` on the listing page tells you which products are worth opening for reviews, before you spend anything opening them.

### How it compares

| | this actor | `extractify-labs/emag-scraper` | `gio21/emag-ro-scraper` |
|---|---|---|---|
| Per 1.000 products | **$1,00** | $1,00 | $3,00 |
| Actor-start fee | $0,001 | **$0,00005** | none |
| Markets | **RO, BG, HU** | RO | RO |
| Full untruncated title | **yes** | not stated | not stated |
| `offer_id` (who currently wins the listing) | **yes** | no | no |
| Category id and trail | **yes** | no | no |
| Monthly users | new | **12** | 1 |

Honest about the other side: `extractify-labs` has twelve monthly users and this actor has none, which is the only column in that table that favours them and it is not a small one. Their start fee is also lower, so on a run of a handful of rows they come out slightly cheaper. On the row price the two are identical.

### Pricing

Two events. `run_start` costs $0,0010 per run. `product` costs $0,0010 per product written to the dataset, which is $1,00 per 1.000. Pages that stay blocked never reach the dataset and are never billed. All pricing is pay-per-event, with no per-compute-unit charges.

### Limits and gotchas

- **There is no was/now price on eMAG listing pages.** Four categories were checked across all three markets and eMAG renders no struck-through price at all on search results. Rather than ship a column that is null on every row of every run, this actor does not return one. Where a discount exists it is on the product page, which this actor does not open.
- **Pagination is a path segment, in two different places.** A search URL takes it on the end (`/search/laptop/p2`); a category URL ends in `/c` and takes it *before* that (`/laptopuri/p2/c`), which is what eMAG's own next-page link uses. `/laptopuri/c/p2` is a hard 404. The actor handles both, so paste the plain URL and let it page.
- **`site_position` is not `position`.** The first is the rank eMAG gave the card on its own page, the second is the rank in your merged, deduplicated result set. They diverge as soon as more than one page is fetched.
- **Sponsored placements are not separated.** eMAG mixes promoted cards into the grid and does not mark them in a way that survives to the listing markup, so they arrive as ordinary rows. `badges` is the closest signal.
- **Ratings only exist where reviews do.** Roughly three quarters of rows carry one; the rest are new or low-traffic listings, and they come back as null rather than zero.
- **Use the market's own language.** Searching `laptop` works everywhere because it is a loanword, but a Romanian term on `emag.hu` returns little. The site does not translate for you.

### FAQ

**Can I scrape eMAG without an API key?**
Yes. The listing page is served to anonymous visitors and that is what this actor reads.

**Does it cover Bulgaria and Hungary?**
Yes, through the `market` field. Each is fetched from an exit in its own country and returns its own currency.

**Why is there no discount or old price?**
Because eMAG does not show one on listing pages. Returning an always-empty column would suggest the data exists somewhere in this output, and it does not.

**What is the difference between `product_id` and `offer_id`?**
The product id identifies the product and is stable. The offer id identifies the seller's offer currently winning that listing, so a change means the listing changed hands.

**How many products can I get in one run?**
Up to 1000, which is about 17 pages.

**Are the prices what a local shopper sees?**
Yes. Prices and stock come back for the market you select, because eMAG varies them by country.

### Related Actors

- [Beslist.nl Scraper](https://apify.com/s-r/beslist-scraper) — Dutch price comparison with EAN barcodes
- [Amazon Buy Box Monitor](https://apify.com/s-r/amazon-buybox) — who currently wins an Amazon listing, across 15 marketplaces
- [Google Shopping Scraper](https://apify.com/s-r/free-google-shopping-scraper---extract-offers-from-any-ean-sku) — offers from every merchant for an EAN or SKU

# 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`):

Which eMAG site to read. This also sets the exit country and the 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, each with a rotated user agent and TLS fingerprint.

## Actor input object example

```json
{
  "query": "laptop",
  "market": "ro",
  "url": "https://www.emag.ro/laptopuri/c",
  "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, its currency and the exit country.

## `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",
    "market": "ro",
    "limit": 60,
    "retries": 3
};

// 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",
    "market": "ro",
    "limit": 60,
    "retries": 3,
}

# 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",
  "market": "ro",
  "limit": 60,
  "retries": 3
}' |
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/n6aoi5bXGcK7iBIWz/openapi.json
