# eBay Spain Scraper — Active Listings (`reventadata/ebay-scraper`) Actor

Searches ebay.es and returns every active listing with its price, auction state, bid count, shipping and ship-from country as separate columns — plus the seller type eBay prints on the card, as a field you can filter on. Active listings only.

- **URL**: https://apify.com/reventadata/ebay-scraper.md
- **Developed by:** [reventadata](https://apify.com/reventadata) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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?

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

> **Unofficial.** This Actor is not affiliated with, authorised by, endorsed by, or in any
> way officially connected to eBay. "eBay" is a trademark of its respective owner and is
> used here only to describe what this tool reads.

eBay Spain Scraper searches [ebay.es](https://www.ebay.es) and returns every **active**
listing it finds as a row: price, auction state, bid count, shipping, ship-from country —
and **the seller type eBay prints on the card, as a first-class field you can filter on**.

### What data can you extract from eBay Spain (ebay.es)?

Give it one or more search terms. It walks eBay's own results pages and returns the
listings as structured columns instead of a page of cards. Auctions and fixed-price
listings are modelled apart, and where an auction also carries a Buy It Now price both are
returned in separate columns. Everything is read from eBay's own server-rendered search
pages — no browser, no login, no item pages opened.

**Sold and completed listings are not available.** See the section of that name below,
before you decide whether this Actor is the one you want.

### Why scrape eBay Spain (ebay.es)?

- **Seller type as a real column.** eBay states on every card whether the seller is a
  private individual or a business (*Vendedor particular* / *Vendedor profesional*). This
  Actor returns that as `sellerType` on every row and lets you keep only one kind. It is
  the field that separates a dealer's stock from a person clearing out a drawer, and it is
  usually the first thing a pricing or sourcing analysis needs.
- **Auctions modelled properly.** `isAuction`, `bidCount`, `timeLeft` and `endsAt` are
  separate fields, and an auction's current bid is never silently mixed with a fixed
  price — `buyItNowPrice` is its own column.
- **Shipping split from its wording.** `shippingCost` is a number and `freeShipping` is a
  boolean, while `shippingText` keeps eBay's own phrase so you can tell a quoted cost from
  an estimate, and free delivery from free local pickup.
- **You control the cost.** `itemsPerPage` is the lever: at 240 listings per page a run
  makes a quarter of the requests it makes at 60, for exactly the same rows.
- **It stops when the results stop.** Past the end of a result set eBay does not stop and
  does not error — it re-serves one identical padded page forever. This Actor works out
  the real end from eBay's own result count and confirms it by noticing when a page adds
  no listing it has not already seen.
- **Private sellers stay private.** Seller *type* is returned for every row; seller
  *identity* only for businesses. See Privacy below.

### Sold and completed listings are not available

If you came here for sold prices, this Actor does not have them, and nothing on this page
should be read as implying otherwise.

- eBay's `LH_Sold` / `LH_Complete` filters redirect an anonymous visitor to a sign-in wall.
  There is nothing to read there without an account, so those controls are not offered.
- eBay's own **Marketplace Insights API**, the official route to completed-listing data,
  is a restricted programme that is not open to new developer applications.

So there is currently no path — official or otherwise — to sold-price data without an
authenticated eBay account session, and this Actor does not have one. What it covers is
**active listings**: what is on sale right now, at what asking price, from whom.

### How to scrape eBay Spain (ebay.es)

1. Put one or more search terms in `searchTerms` — the same words you would type into
   eBay's search box.
2. Optionally narrow with `condition`, `listingType`, a price bound or `sellerType`.
3. Leave `itemsPerPage` at 240 unless you have a reason not to; it is the cheapest setting.
4. Run it. Results land in the dataset.

#### Input

| Field | Type | Meaning |
|---|---|---|
| `searchTerms` | array | One or more keywords. Required. |
| `condition` | string | `any` | `new` | `refurbished` | `used` | `forParts`. Applied by eBay, server-side. |
| `listingType` | string | `any` | `auction` | `buyItNow`. |
| `sellerType` | string | `any` | `private` | `business`. Applied after fetching — eBay publishes no search parameter for it. |
| `minPrice` / `maxPrice` | integer | Optional, in whole euros. Omitted entirely when empty — never defaulted. |
| `itemsPerPage` | string | `"60"` | `"120"` | `"240"`. The cost lever. |
| `maxItems` | integer | Hard cap on rows pushed. Default 240. |
| `maxPages` | integer | Guard rail on pages per search term. The Actor already stops at the real end of a query. |

```json
{
    "searchTerms": ["iphone 15 pro"],
    "condition": "used",
    "sellerType": "private",
    "minPrice": 200,
    "maxPrice": 600,
    "itemsPerPage": "240",
    "maxItems": 1000
}
```

#### Output

```json
{
    "listingId": "147520935178",
    "title": "Apple Iphone 15 Black 128GB (ohne Simlock)",
    "url": "https://www.ebay.es/itm/147520935178",
    "keyword": "iphone",
    "page": 1,
    "condition": "Usado",
    "sellerType": "business",
    "price": 369,
    "currency": "EUR",
    "buyItNowPrice": null,
    "isAuction": false,
    "bidCount": null,
    "timeLeft": null,
    "endsAt": null,
    "isBuyItNow": true,
    "acceptsBestOffer": false,
    "shippingCost": 0,
    "freeShipping": true,
    "shippingText": "Envío gratis",
    "shipFrom": "Austria",
    "energyClass": "B",
    "sellerUsername": "marketxpres",
    "sellerFeedbackPercent": 100,
    "sellerFeedbackCount": 169
}
```

An auction row instead carries `isAuction: true`, a `bidCount`, a `timeLeft` such as
`"Quedan 2 d 3 h"` and an `endsAt` such as `"(Lunes, 23:25)"`, with `price` holding the
current bid and `buyItNowPrice` the fixed price if the seller offers one.

### Notes and limits

- **eBay's `robots.txt` disallows the `/sch/` search path**, which is the surface this
  Actor reads, and eBay's preamble asks that automated access go through its official API
  instead. That is stated here plainly so you can weigh it yourself; it is a policy
  question, not a technical one, and no wording in this README changes it.
- **`condition` is eBay's own Spanish wording, verbatim** — `Usado`, `Totalmente nuevo`,
  `Reacondicionado`, `Solo piezas`, `Abierto, sin usar` and others. It is not mapped to a
  grade, because eBay's full condition vocabulary has not been measured and a half-known
  enum would quietly mislabel listings.
- **`condition` and `sellerType` can both be null.** A seller may replace the card's
  subtitle with marketing copy, in which case eBay publishes neither for that listing and
  this Actor invents neither.
- **`energyClass` is the EU energy label**, A to G — the same scale a fridge carries. It
  is not a condition grade and says nothing about wear.
- **`price` means two different things**, and `isAuction` is how you tell which: the
  asking price on a fixed-price listing, the current bid on an auction. eBay renders both
  in the same element.
- **`endsAt` is a weekday and a clock time**, not a date, because that is all eBay
  publishes on the results page.
- **A search term with no matches is recognised from eBay's own results heading.** eBay
  fills an empty result page with sixty unrelated but genuinely priced listings; none of
  it is collected, and the run says so in the log rather than returning stock you did not
  ask for.
- **A run that returns zero rows fails rather than succeeding empty**, so a silent
  misconfiguration cannot look like "no stock today".
- **eBay sometimes answers the same URL with a page that has no listings on it
  at all** — a full-size page carrying a results heading and a category tree but
  not one listing card. It is not a block and not an empty result; another
  attempt on the same URL returns the ordinary results page. This Actor
  recognises that page and retries rather than reporting it as zero results, and
  it says in the log if a page was skipped after every retry, so a short run is
  visible rather than silent. It happens at every page size, so turning
  `itemsPerPage` down does not avoid it — that was measured both ways.
- **Every request goes through Apify Unblocker, and there is no cheaper option.** eBay
  answers a plain request, a datacenter proxy and a residential proxy alike with an
  identical canned error page — measured over 25 pages each, zero successes — because it
  runs a JavaScript browser challenge in front of search. Unblocker's per-request price is
  therefore the floor for this Actor, which is why `itemsPerPage` matters so much.

### Privacy

eBay states on every search card whether the seller is registered as a business or as a
private individual. GDPR protects natural persons, not legal entities, so this Actor
treats the two differently:

- **Private sellers:** `sellerType` is returned as `private`. `sellerUsername`,
  `sellerFeedbackPercent` and `sellerFeedbackCount` are **absent from the row entirely** —
  not empty, absent. An eBay username is pseudonymous, but a private seller's handle
  identifies one individual across every listing and feedback page on the site.
- **Business sellers:** those three fields are returned, because a shop is a business, not
  a natural person.
- **A listing whose card carries no seller sentence** is treated as private, because
  withholding identity from a business by mistake costs a column and the opposite mistake
  does not undo.

No real name, phone number, email address or street address is collected for anyone; eBay
publishes none of those on its search pages.

### How much does it cost to scrape eBay Spain (ebay.es)?

Pricing is pay-per-event: **$0.001 per result** written to the dataset (**$1.00 per 1,000
results**), plus **$0.00005** charged once when the run starts. There is no subscription —
you pay only for what you get.

### FAQ

**Is a proxy required to scrape eBay Spain?** Yes, and there is no cheaper option: eBay
answers a plain request, a datacenter proxy and a residential proxy alike with an identical
canned error page — measured over 25 pages each, zero successes — because it runs a
JavaScript browser challenge in front of search. This Actor routes every request through
Apify Unblocker, wired in internally with no `proxyConfiguration` input to set.

**Can I get sold or completed listings?** No. eBay's `LH_Sold` / `LH_Complete` filters
redirect an anonymous visitor to a sign-in wall, and eBay's Marketplace Insights API — the
official route to that data — is a restricted programme not open to new developer
applications. This Actor covers active listings only.

**Is seller identity included in the output?** Only for business sellers. Private-seller
rows never carry `sellerUsername`, `sellerFeedbackPercent` or `sellerFeedbackCount` — those
fields are absent from the row, not empty.

### More second-hand marketplace scrapers

| Actor | What it scrapes |
|---|---|
| [Wallapop Scraper](https://apify.com/reventadata/wallapop-scraper) | Wallapop, Spain — any keyword, phone mode with battery-health parsing |
| [Wallapop + Vinted + Milanuncios Scraper — Spain](https://apify.com/reventadata/wallapop-vinted-milanuncios-scraper) | Wallapop, Vinted and Milanuncios in Spain — one keyword, one merged table |
| [Cash Converters Spain Scraper](https://apify.com/reventadata/cashconverters-scraper) | Cash Converters Spain — graded second-hand stock with price when new |
| [Milanuncios Scraper](https://apify.com/reventadata/milanuncios-scraper) | Milanuncios — 17 categories, condition, storage and battery-health filters |
| [Gumtree UK Scraper](https://apify.com/reventadata/gumtree-scraper) | Gumtree UK — any category, vehicle and phone details parsed from the ad's own text |
| [OLX Romania Scraper](https://apify.com/reventadata/olx-ro-scraper) | OLX Romania (olx.ro) — any keyword, no proxy required |
| [OLX Brazil Scraper](https://apify.com/reventadata/olx-scraper) | OLX Brazil (olx.com.br) — any category, structured attributes from the site's own detail chips |
| [Vinted Spain Scraper](https://apify.com/reventadata/vinted-scraper) | Vinted Spain (vinted.es) — any keyword or category, no proxy required |
| [Todocolección Scraper](https://apify.com/reventadata/todocoleccion-scraper) | Todocolección — collectibles, antiques and auctions, fixed-price and bidding modelled apart |
| [Back Market Spain Scraper](https://apify.com/reventadata/backmarket-scraper) | Back Market Spain — refurbished phones, laptops and tablets, priced against new |

# Actor input Schema

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

One or more keywords to search ebay.es for, exactly as you would type them into the site's own search box. Each term is searched in turn and every row records the term it came from.

## `condition` (type: `string`):

eBay's own condition filter, applied server-side. Each of these four values was confirmed on a live search by both a changed result count and a homogeneous condition on the returned cards. Condition values eBay offers but that were never measured are deliberately not exposed.

## `listingType` (type: `string`):

Auctions only or fixed-price only, using eBay's own filters. Note that an auction can also carry a Buy It Now price; when it does, both are returned in separate columns.

## `sellerType` (type: `string`):

Keep only private sellers or only business sellers, using the wording eBay prints on the card itself (Vendedor particular / Vendedor profesional). Applied after fetching, because eBay publishes no search parameter for it. A listing whose subtitle carries seller marketing copy instead of a seller sentence has no seller type and matches neither value.

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

Optional. Omitted from the request entirely when empty — never defaulted to 0. Sent as eBay's own \_udlo filter.

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

Optional. Sent as eBay's own \_udhi filter. Unlike some marketplaces, ebay.es accepts both price bounds in the same request.

## `itemsPerPage` (type: `string`):

How many listings eBay puts on each page it serves. This is the main cost lever: at 240 a run makes a quarter of the requests it makes at 60, for exactly the same rows. Only the three values confirmed to change the number of rendered cards are offered — eBay quietly serves 240 for any other value, so a fourth choice would be a control that does not do what its label says.

## `maxItems` (type: `integer`):

Hard cap on rows pushed across all search terms.

## `maxPages` (type: `integer`):

A guard rail on top of the real stopping rule. The Actor already stops at the end of a query, which it works out from eBay's own result count and confirms by noticing when a page adds no listing it has not already seen — eBay pads past the end rather than stopping, so it never runs out of cards on its own.

## Actor input object example

```json
{
  "searchTerms": [
    "iphone 15 pro"
  ],
  "condition": "any",
  "listingType": "any",
  "sellerType": "any",
  "itemsPerPage": "240",
  "maxItems": 240,
  "maxPages": 500
}
```

# Actor output Schema

## `matches` (type: `string`):

Matching listings: price, auction state, bid count, seller type, shipping and a link to the listing.

# 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 = {
    "searchTerms": [
        "iphone 15 pro"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("reventadata/ebay-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 = { "searchTerms": ["iphone 15 pro"] }

# Run the Actor and wait for it to finish
run = client.actor("reventadata/ebay-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 '{
  "searchTerms": [
    "iphone 15 pro"
  ]
}' |
apify call reventadata/ebay-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reventadata/ebay-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/yKU7q42YdQ0fzyXBW/builds/0qB709PEfXAgG3NtO/openapi.json
