By the end of this tutorial, you will have a short Python script that turns a Back Market Spain category into structured rows: model name, starting price, the price the device cost new, star rating, review count and condition grade.

Back Market sells refurbished electronics, and backmarket.es sits behind Cloudflare Bot Management. In recon on 2026-08-22, requests with no proxy, datacenter and residential proxies got an error body instead of a page; Apify Unblocker returned the page.

In this guide, we use Python and the Apify Actor Back Market Spain Scraper (reventadata/backmarket-scraper). You will pick a category, run the Actor and narrow rows by price and brand. The sample row below is a sample from a capture dated 2026-09-18, parsed with the Actor's own parser.

⏱️ Time estimate: one sitting: install one package, set one token, run three short Python blocks.

Prerequisites

To follow this tutorial, you will need:

  1. Python 3.11+ (apify-client 3.3.0 requires it).
  2. The Apify API client for Python.
  3. An Apify account and its API token, which sits on the API & Integrations page in Apify Console.

Install the client:

pip install "apify-client~=3.0" 

The code reads the token from an environment variable:

export APIFY_API_TOKEN="your_apify_token_here" 

What you'll achieve

You will turn one Back Market category page into one row per product model. A card on that page is a model, "iPhone 16", not a merchant offer. Each row carries id, title, url, brand, model, price (the cheapest variant, the site's Desde), priceWhenNew, rating, ratingCount, availableColors, grade, availableStock and warrantyMonths.

Note: Back Market names no merchant on its category or product pages, so rows carry no seller data. The payload holds a sellerId; the Actor never reads it.

Example use case: Suppose you want refurbished iPhones that start between 300 and 700 EUR. You would put the iPhone category URL in categories, set minPrice: 300 and maxPrice: 700, and every saved row would have a starting price inside that band, with its price new beside it.

Step 1: Access Back Market Spain Scraper

Go to the Back Market Spain Scraper page on Apify Store. The README lists every input and output field. In this tutorial we drive the Actor from Python, so everything after this step happens in code.

Step 2: Configure the category

The one required input is categories: one or more Back Market Spain category URLs, copied from the address bar. A category is a slug and the UUID after it, for example iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39; a slug alone is an error. The Actor checks every category before its first request.

There is no free-text search input: backmarket.es's robots.txt disallows */search, so the Actor browses categories only.

Each category page holds 31 models. maxPages (1 to 33, default 33) bounds pages per category, and maxItems (default 300) caps rows across all categories.

Here is the setup. It reads one page of the iPhone category and saves at most 10 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 category: one page, at most 10 rows run_input = { "categories": [ "https://www.backmarket.es/es-es/l/iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39" ], "maxPages": 1, "maxItems": 10, } 

No proxy setting exists: the Actor always fetches through Apify Unblocker, the one transport that reached the site in recon.

Step 3: Add optional filters

Back Market's own price, storage, model and colour controls write a URL fragment (#price=600&price=900). A fragment never leaves the browser, and the query-string spelling of the same filters is ignored by the server. So the Actor sends only ?p=<n> and applies its filters to the rows it read:

Parameter Type What it keeps
minPrice / maxPrice integer Rows whose starting price, in whole euros, is inside the bound.
brands array Rows whose brand matches, using the site's lowercase value such as apple or samsung, case-insensitive.
fetchProductDetails boolean Not a filter: opens each model's product page for its variant price matrix. Off by default.

⚠️ minPrice greater than maxPrice stops the run with an error at its start. A negative price is refused before the run starts.

Step 4: Run the Actor

Start the run with .call(). It starts the Actor on Apify and waits for it to finish before returning the run:

# 3. Start the Actor run and wait for it to finish print("Starting Back Market extraction...") run = client.actor("reventadata/backmarket-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 models items = client.dataset(run.default_dataset_id).list_items().items print(f"Retrieved {len(items)} models.\n") 

Behind the scenes:

  • The Actor builds the category URL with ?p=N and fetches each page through Apify Unblocker.
  • It reads the models from the page's embedded __NUXT_DATA__ payload, keeps the 31 grid records and skips the recommendation carousel, applies your filters and saves the rows.
  • It stops at maxItems, at maxPages, or once it has every model the site reports for the category.

Step 5: View and download results

Loop over the rows to print the parts you need:

for item in items: print(item["title"], "|", item.get("grade")) print(f" From {item['price']} EUR, new {item['priceWhenNew']} EUR") if item.get("price") and item.get("priceWhenNew"): print(f" {1 - item['price'] / item['priceWhenNew']:.0%} below the price new") print(f" Rating {item.get('rating')} from {item.get('ratingCount')} reviews") print("-" * 40) 

Here is one row, a sample from a capture dated 2026-09-18 (page 1 of the iPhone category) parsed with the Actor's own parser:

{ "id": "5119391", "title": "iPhone 16", "url": "https://www.backmarket.es/es-es/p/iphone-16", "category": "iphone", "brand": "apple", "productType": "Smartphone", "model": "iphone 16", "variationId": "0e3441cf-37af-429e-973f-218cb3c1fd63", "price": 584.0, "priceWhenNew": 1009.0, "currency": "EUR", "isStartingFromPrice": true, "rating": 4.59, "ratingCount": 14802, "availableColors": [ "Azul ultramar", "Blanco", "Negro", "Rosa", "Teal", "Ultramarine", "Verde azulado" ], "imageUrl": "https://d2e6ccujb3mkqf.cloudfront.net/2d99b692-a008-4345-8196-fea4cf5e4096-1_44974392-bda8-41f2-835b-2410a15bd88a.jpg", "grade": "Excelente", "availableStock": 29, "warrantyMonths": 24 } 

Apify datasets export as JSON, CSV, XML, Excel, HTML table, RSS or JSONL.

Understanding your results

The fields that matter most:

  • price and isStartingFromPrice: the cheapest variant of the model, in EUR, not the price of one device. The real price depends on grade, battery, storage, colour and SIM. All 31 captured rows had isStartingFromPrice: true, and starting prices ran from 81.99 (iPhone 7) to 1,180 (iPhone 17 Pro Max).
  • priceWhenNew: what the site says the device cost new, taken from the card. In the capture, the starting price sat between 20% (iPhone 17 Pro Max) and 78% (iPhone 12) below it; that percentage is our arithmetic, not a site field.
  • grade, availableStock, warrantyMonths: they describe the listing behind the starting price. Grades are the site's Spanish words: of 31 rows, 17 Correcto, 8 Muy bueno, 5 Excelente, 1 Prémium. All 31 had 24 months.
  • rating, ratingCount: two decimals out of 5. 30 of 31 were rated, 4.07 to 4.68; iPhone 15 Pro showed 0 with 0 reviews.
  • variationId: the priced listing's public UUID; it changes when another offer becomes the cheapest.

Pro tips

  1. Use leaf categories. The site serves at most 1,000 models per category: 32 pages of 31 plus a page of 8. Past that it shows 0 productos, even on electronica, which reported 6,588 on page 1 (2026-09-18). The Actor stops at page 33 and logs the site's count, so for a large category, run its sub-categories.
  2. Batch categories in one run. Put several URLs in categories. A model saved under one is not saved again under the next.
  3. Turn on details only when you need them. fetchProductDetails adds currentPrice, grades, batteryOptions, storageOptions, colorOptions and simOptions, each option with a label, the site's value and a price, at one extra page request per model. A product page that fails keeps its listing row.
  4. Filters narrow what you receive, not what is read. A tight maxPrice on a large category still walks its pages; set maxPages to bound it.
  5. Start small. Keep maxPages: 1 and maxItems: 10 until your code handles the fields.

Troubleshooting

  • Zero rows: the status message names the reason. A page that failed or a category that could not be resolved ends the run FAILED. Rows rejected by your filters, or a category the site itself reports as 0 productos, end it SUCCEEDED with an empty dataset.
  • Run stops at once: a category is a slug without its UUID, or minPrice is above maxPrice.
  • Fewer rows than expected: read the end of the log. Rejected N: ... lines count each filter's drops, and Pushed N row(s). the saved rows. From Python: client.log(run.id).get().
  • A large category stops at 1,000: the site's window, not a fault. Use sub-categories.

For help, use the Actor's Store page.

Pricing and next steps

For current pricing and details, see the Store page.

To track prices, save the input as a task and add an Apify schedule, which runs an Actor or task on a cron expression (0 8 * * * is every day at 8 AM). To react when a run finishes, add a webhook on ACTOR.RUN.SUCCEEDED; its payload carries the run object, including defaultDatasetId.

Which refurbished categories are you planning to track on Back Market? Drop a comment! 💬