Sitemap
ReventaData

ReventaData

Web scraping, API reverse-engineering & automated data feeds at scale. Building turnkey marketplace scrapers on Apify: apify.com/reventadata

How I Got eMAG.ro Prices as Data in Minutes Using eMAG.ro Scraper (Step-by-Step)

9 min read3 hours ago

--

Press enter or click to view image in full size

I build and publish scrapers on the Apify Store, and one of them reads eMAG.ro: eMAG.ro Scraper. Here is how to run it from Python, with real rows from a page I captured on 2026–10–21.

eMAG.ro is a Romanian online store, and its listing pages are written for people: a product name, a price like 3.100,00 Lei, sometimes a recommended price, a star rating, a stock note and labels. Copying those into a spreadsheet means opening page after page.

Writing your own scraper runs into walls. eMAG sits behind an AWS WAF captcha: when I tested it on 2026–10–21, plain, datacenter and Apify Unblocker requests got a captcha page. Its search is fuzzy. And past the last real page of a listing, the site quietly serves a broader search under the same kind of address, so a simple page counter mixes two result sets.

So I built a dedicated cloud Actor (an Actor is a small program that runs on Apify’s servers): eMAG.ro Scraper (reventadata/emag-scraper). It fetches pages through Apify residential proxies in Romania, which reached real pages that day, and turns each product card into one row. It is unofficial and not affiliated with eMAG.

Below: setup, a Python run, and the rows; the sample comes from a capture, not a run.

Why I needed this solution

The goal: an eMAG.ro search in, one clean row per product out. Three things make that harder.

  • Prices are text in Romanian format. The page prints 3.100,00 Lei, with a dot for thousands and a comma for decimals. The row carries it as the number 3100.0, and the recommended retail price (PRP) printed under it as listPrice.
  • Stock is a code, not a sentence. Many cards show no stock text at all: on my captured page, 48 of 60 had none. The Actor reads the card’s stock code and names it: IN_STOCK, LIMITED_STOCK, PREORDER, OUT_OF_STOCK and so on.
  • Listings end in a confusing way. A listing stops at 100 pages, and past its last real page the site switches to a broader search. The Actor reads the site’s own result count on page 1 and stops where the result set changes, so two searches never mix.

What you’ll need

  • An Apify account: sign up on apify.com.
  • Your Apify API token: it is on the API & Integrations page in Apify Console (Console is your Apify workspace in the browser).
  • Python 3.11 or newer, with the official apify-client library (version 3.3.0 requires 3.11+).
  • Time: one sitting. You install one package, set one token, and run three short code blocks.
  • Pricing: For current pricing and details, see the Store page.
  • Optional: a spreadsheet app for CSV or Excel results.

No scraping code of your own is needed: the Actor does the fetching and parsing.

What you’ll accomplish

You will turn an eMAG.ro search or category into rows with:

  • Identity and link: id (the offer id), name and url.
  • Price: price in lei as a number, with currency: "RON" (Romanian lei).
  • Discount: listPrice (the PRP on the card), previousPrice (a struck-through price, when shown) and discountPercent.
  • Reviews: rating (average stars) and reviewCount.
  • Stock: availability, inStock (true when you can buy it now) and the card's own stockLabel.
  • Context: category, categoryTrail (the category path), badges and position on the page.
  • Source: matchedKeyword and page, the search and result page that produced each row.

Step-by-step walkthrough

Step 1: Get started with eMAG.ro Scraper

First, open the eMAG.ro Scraper page on Apify Store. Its README lists every input and output field. Then install the Python client:

pip install "apify-client~=3.0"

And set your API token in your terminal:

export APIFY_API_TOKEN="your_apify_token_here"

💡 My tip for Step 1: keep the token in an environment variable, as above, never pasted into a script. Apify’s docs also advise never using one token for several services.

Step 2: Tell the tool what you want

The main input is searchQueries: the words you would type in eMAG's search box, one search per term. Think of it as a list of searches the Actor runs for you, one after another. If you already have a category page open, paste its address into startUrls instead, for example https://www.emag.ro/laptopuri/c.

Get ReventaData’s stories in your inbox

Join Medium for free to get updates from this writer.

Here is the setup for this guide. It searches iphone, reads one result page, and saves at most 20 rows:

import os
from apify_client import ApifyClient
# 1. Initialize ApifyClient with the token from the environment
client = ApifyClient(os.environ["APIFY_API_TOKEN"])
# 2. Define the search: one term, one result page, at most 20 rows
run_input = {
"searchQueries": ["iphone"],
"maxItems": 20,
"maxPagesPerListing": 1,
}

What each setting means:

  • searchQueries: the terms to search.
  • maxItems: the most rows to save in total.
  • maxPagesPerListing: how many result pages to read per term. Each page holds up to 60 products.

💡 My tip for Step 2: expect loose matches. On the page I captured, eMAG answered iphone with its whole "Telefoane Mobile" category, and 3 of the 60 products were other phones. Turn on exactMatchesOnly: true to keep only names that contain every word you typed.

Step 3: Fine-tune your settings (optional)

These settings shape what gets saved:

  • sort: the order on the site, such as PRICE_ASC (cheapest first), REVIEWS or DISCOUNT.
  • minPrice / maxPrice: a price band in lei. It is also sent to the site, so it narrows what eMAG returns.
  • minRating: only products rated at or above a value; products with no reviews are dropped.
  • minDiscountPercent: only products at least that many percent under their struck-through price or PRP.
  • onlyInStock: only products you can buy now.
  • includeKeywords / excludeKeywords: words the name or category must or must not contain.

Honestly, for a first run you need none of them; the defaults read the search as eMAG orders it.

💡 My tip for Step 3: when you sort by price, combine it with a word filter. On a page I captured of iphone sorted cheapest first, the first three products were a Nokia 105, a MaxCom phone and a replacement screen for an iPhone.

Step 4: Add extra features (if you want)

There is no extra detail switch here; the extra power is in reaching further. A single listing stops at 100 pages, which is 6,000 products. To go past that in a big category, split it into price bands (for example 0 to 1,000 lei, then 1,000 to 2,000) and run each band; because the band is sent to the site, each one is paged as its own listing.

💡 My tip for Step 4: a category address from startUrls reads exactly that category, which avoids the fuzzy search altogether.

Step 5: Hit the Start button

In code, “Start” is one call. It starts the Actor on Apify’s servers and waits until it finishes:

# 3. Start the Actor run and wait for it to finish
print("Starting eMAG.ro extraction...")
run = client.actor("reventadata/emag-scraper").call(run_input=run_input)
if run is None or run.status != "SUCCEEDED":
status = run.status if run else "unknown"
raise SystemExit(f"Run did not succeed (status: {status}).")
# 4. Fetch the saved products
items = client.dataset(run.default_dataset_id).list_items().items
print(f"Retrieved {len(items)} products.\n")

Behind the scenes, the Actor builds the eMAG search address, fetches each page through a residential proxy in Romania, reads each product card, skips products it has already saved, applies your filters, and saves the rows to a dataset (Apify’s table of results).

💡 My tip for Step 5: keep the status check. A blocked or unreadable run with no rows ends as FAILED; a search that loads but matches nothing ends as SUCCEEDED with a status message.

Step 6: Check out your results

Print what you need from each row:

for item in items:
print(item["name"])
print(f" Price: {item['price']} {item['currency']}")
if item.get("discountPercent") is not None:
reference = item.get("previousPrice") or item.get("listPrice")
print(f" Discount: {item['discountPercent']}% under {reference}")
print(f" Stock: {item['availability']}")
print(f" Rating: {item.get('rating')} ({item.get('reviewCount')} reviews)")
print("-" * 40)

Here is one row, a sample from a capture dated 2026–10–21 (page 1 of the iphone search), parsed with the Actor's own code. A saved row also carries scrapedAt, the time it was saved:

{
"id": "83822499",
"productId": "40684424",
"sku": "DQFCMXMBM",
"name": "Telefon mobil Apple iPhone 13, 128GB, 5G, Midnight",
"url": "https://www.emag.ro/telefon-mobil-apple-iphone-13-128gb-5g-midnight-mlpf3rm-a/pd/DQFCMXMBM/",
"price": 3100.0,
"currency": "RON",
"listPrice": 3863.87,
"previousPrice": null,
"discountPercent": 20,
"rating": 4.81,
"reviewCount": 1101,
"availability": "IN_STOCK",
"inStock": true,
"stockLabel": "în stoc",
"category": "Telefoane Mobile",
"categoryTrail": [
"Laptop, Tablete & Telefoane",
"Telefoane mobile & accesorii",
"Telefoane Mobile"
],
"badges": [
"Top Favorite"
],
"hasGeniusBadge": true,
"image": "https://s13emagst.akamaized.net/products/40685/40684414/images/res_974dc0e4b6a1180b612c24afef8eb494.jpg?width=720&height=720&hash=27A6CA20492D41746C0C8A0226456760",
"position": 26,
"matchedKeyword": "iphone",
"page": 1
}

în stoc means "in stock", and discountPercent: 20 is the drop from the PRP of 3,863.87 lei to the price of 3,100 lei.

💡 My tip for Step 6: read stockLabel together with availability. The label is the card's own words and is often missing; the code is always there.

Step 7: Download and use your data

Apify datasets export as JSON, CSV, XML, Excel, HTML table, RSS or JSONL. CSV or Excel opens straight in a spreadsheet; JSON suits code. In Python, iterate_items() takes a fields argument to fetch only the columns you want.

💡 My tip for Step 7: keep id in every export. It is the offer id, and it is what lets you line up the same product across two days.

The results that surprised me

I parsed the captured page, 60 products from page 1 of the iphone search on 2026-10-21, with the Actor's own parser. Four things stood out in those 60 rows.

First, availability. 36 products were IN_STOCK, 6 LIMITED_STOCK and 18 PREORDER; none were supplier stock, out of stock, showroom only or unknown. So inStock was true on 42 of the 60.

Second, the pre-orders. All 18 were new models: 8 iPhone Duo listings, 7 iPhone 18 Pro Max and 3 iPhone 18 Pro. The 8 iPhone Duo rows had no rating and no review count yet.

Third, discounts were rare. Only 10 of the 60 cards showed a PRP, none showed a struck-through price, and the drops ran from 9% to 33%. Not one pre-order was among them.

Fourth, the search was wider than the word. Every row sat in the “Telefoane Mobile” category, and 3 were not iPhones at all: a mini XS18 Pro, a Honor 600 Pro and a ZTE nubia Focus 2 Ultra. Prices ran from 236.91 lei to 19,999 lei, and the most-reviewed product was an iPhone 11 with 1,639 reviews.

A sample of 60 rows is one page, not the market; it shows what the fields look like, not a trend.

Tips I learned along the way

  1. Narrow on the site, then filter. A category address, a price band or a sort changes what eMAG returns. Rating, discount and word filters only drop rows after reading, so a narrow filter on a broad search reads many pages for few rows.
  2. Use exact matches for brand searches. exactMatchesOnly dropped the 3 non-iPhones from my captured page and kept the other 57.
  3. Run several searches at once. Put several terms in searchQueries; a product saved under one term is not saved again under the next.
  4. Start small. maxPagesPerListing: 1 and maxItems: 20 first; once your spreadsheet or code handles every field, scale up step by step.
  5. Schedule it. Save your input as a task and add an Apify schedule to it; it reruns the search on a cron expression (a timetable format: 0 8 * * * is every day at 8 AM).

What I built with this data

I sorted the 10 discounted rows of my capture by discountPercent, highest first. The top five:

Product Price (lei) PRP (lei) Discount Stock Apple iPhone 12, 64GB, Black 2,049 3,048.99 33% IN_STOCK Apple iPhone SE 3, 64GB, Midnight 1,799 2,644.99 32% LIMITED_STOCK mini XS18 Pro, 16GB 236.91 325.49 27% IN_STOCK ZTE nubia Focus 2 Ultra, 512GB 1,199.99 1,599.99 25% LIMITED_STOCK Apple iPhone 13, 128GB, Midnight 3,100 3,863.87 20% IN_STOCK

All five discounts are measured against the PRP; the two deepest are an iPhone 12 and an iPhone SE 3.

Conclusion

Before: an eMAG.ro search is pages of cards with prices in Romanian number format and stock hidden in codes. After: one call to eMAG.ro Scraper returns rows with names, prices in lei as numbers, PRP and discount, ratings and a named stock status. Start with one page, then add a sort, price band or category.

About the author: reventadata builds and maintains marketplace and online-store scrapers on the Apify Store.

👏 If this helped you, please clap and follow for more tutorials!

--

--