# eBay Product Scraper (`incognito_mode/ebay-product-scrapper`) Actor

Fast, reliable eBay scraper. Extract product search results, pricing, shipping, seller specs, item condition & high-res images to JSON/Excel. No API key needed.

- **URL**: https://apify.com/incognito\_mode/ebay-product-scrapper.md
- **Developed by:** [Elena Vance](https://apify.com/incognito_mode) (community)
- **Categories:** E-commerce, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 scraped product 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/platform/actors/running/actors-in-store#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

## eBay Product Scraper

Extract eBay listings — price, shipping, condition, seller rating, item specifics, images and descriptions — from keyword searches or direct listing URLs. No eBay API key, no login.

Results are stored in an Apify dataset you can export as **JSON, CSV, Excel, XML or HTML**.

***

### What makes this scraper reliable

eBay does not simply return `403` when it does not like a request. It answers with a **bot challenge page carrying a `200 OK` status** — a page that looks perfectly valid to a naive scraper and parses to zero rows. That is the single most common reason an eBay scraper "succeeds" while returning nothing.

This Actor is built around that reality:

- **Every response is screened before parsing.** Challenge pages, eBay's branded error shell and blocked status codes all retire the current session and retry on a fresh proxy IP and browser fingerprint.
- **A page that loads but parses to nothing is treated as a failure**, not as an empty result — unless eBay's own "no matches found" copy is on the page. Silent zero-row runs are impossible by construction.
- **The two ways of scraping nothing are told apart.** A run where no page could be loaded **fails** with an actionable message; a search that genuinely has no matches **succeeds** and says so. Neither one finishes green with an unexplained empty dataset.
- **Both generations of eBay's markup are supported.** eBay replaced the long-standing `s-item` search layout with `s-card`; the extractor reads either.

### Adaptive crawling: fast when it can be, resilient when it must be

A browser is expensive — memory, time, and on residential proxies, bandwidth. Plain HTTP is roughly an order of magnitude cheaper, but eBay blocks it from some IP pools.

Rather than guessing, the Actor **spends one probe request at startup** to find out which transport eBay accepts for *this* run:

| Result | What happens |
| --- | --- |
| Probe succeeds | Runs on the fast HTTP crawler. Anything that later gets blocked is automatically retried in a real browser. |
| Probe is blocked | Runs on the Playwright browser crawler, which executes the challenge JavaScript. |

In browser mode, images, fonts, media and analytics are blocked before they are fetched, which cuts proxy bandwidth several-fold without losing any scraped data.

You can override the decision with **Crawler mode** (`auto` / `http` / `browser`).

### Consistent, comparable rows

Two details that quietly ruin an eBay dataset are handled for you:

- **Proxy country is pinned to the marketplace.** eBay quotes prices, shipping and currency in the *requesting IP's* locale, so an unpinned residential pool returns rows priced in whatever countries the proxies happened to land in. Searching `ebay.de` pins the pool to Germany unless you choose a country yourself.
- **The list layout is requested explicitly.** Above 60 results per page eBay switches to a gallery card that silently drops seller name, feedback, watch and sold counts. The Actor forces the list layout at every page size, so those fields are present whether you ask for 10 items or 10,000.

***

### Input

Provide a **search keyword**, **start URLs**, or both.

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `search` | array of strings | – | Keywords to search, e.g. `["macbook pro m3"]`. Each is crawled separately. |
| `startUrls` | array | – | eBay search-results URLs or listing (`/itm/...`) URLs. Non-eBay URLs are rejected. |
| `ebaySite` | string | `ebay.com` | Marketplace for keyword searches. 18 regional sites supported. |
| `maxItems` | integer | `2` | Hard cap on stored products. The run stops as soon as it is reached. |
| `maxPagesPerQuery` | integer | `5` | Pagination depth per search. |
| `scrapeDetails` | boolean | `true` | Open each listing page for specifics, galleries, returns and description. |
| `scrapeDescription` | boolean | `true` | Fetch the seller's description iframe (one extra request per listing). |
| `includeDescriptionHtml` | boolean | `false` | Also store the description as raw HTML. |
| `sortBy` | string | `bestMatch` | `bestMatch`, `lowestPrice`, `highestPrice`, `newlyListed`, `endingSoonest`. |
| `itemCondition` | string | `all` | `all`, `new`, `refurbished`, `used`. |
| `listingType` | string | `all` | `all`, `buyItNow`, `auction`. |
| `proxyConfiguration` | object | Apify Proxy, `RESIDENTIAL` | **Residential proxies are strongly recommended.** |
| `crawlerMode` | string | `auto` | `auto`, `http`, `browser`. |
| `maxConcurrency` | integer | `10` | Upper bound on parallel requests. |
| `maxRequestRetries` | integer | `5` | Retries per request, each on a fresh identity. |
| `debugSnapshots` | boolean | `false` | Save page HTML to the key-value store when extraction fails. |

#### Example input

```json
{
  "search": ["macbook pro m3"],
  "ebaySite": "ebay.com",
  "maxItems": 200,
  "scrapeDetails": true,
  "itemCondition": "used",
  "sortBy": "lowestPrice",
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

#### Speed vs. depth

`scrapeDetails: false` is not a stripped-down mode — the search page alone already yields price, original price, shipping, condition, buying format, seller name and feedback, item location, watch counts and the main image. It costs **one request per 60–240 listings** instead of one per listing, so it is dramatically faster and cheaper. Turn details on when you need item specifics, full image galleries, variations, return policy or descriptions.

***

### Pricing

This Actor is **pay per event** — you pay only for what a run actually does, not for compute time:

| Event | Price | When it's charged |
| --- | --- | --- |
| Actor Start | $0.02 | Once per run (scales with memory: 1 event per GB, minimum 1) |
| Scraped Product Result | $0.003 | Once per listing saved to the dataset |

**Worked example:** a default run (2048 MB, `maxItems: 2`) costs 2 × $0.02 for the start event, plus up to 2 × $0.003 for results — **around $0.07**. Turning off `scrapeDetails` doesn't change the per-result price, but it finishes faster and uses less proxy bandwidth per item.

`maxItems` scales the cost linearly — a 100-item run costs about $0.34, a 20-item run about $0.10. These two events are the entire cost of a run — there's no separate platform/compute charge on top. See the **Pricing** tab on this Actor's page for the current, canonical numbers.

***

### Output

One dataset row per listing:

```json
{
  "itemId": "307090982870",
  "url": "https://www.ebay.com/itm/307090982870",
  "title": "Apple MacBook Pro 16\" (512GB SSD, M3 Pro, 18GB) Space Black",
  "subtitle": "Brand New · 16 in · 512 GB",
  "condition": "Brand New",
  "price": 1849.26,
  "priceText": "$1,849.26",
  "currency": "USD",
  "originalPrice": null,
  "shipping": 279.46,
  "shippingText": "+$279.46 shipping estimate",
  "freeShipping": false,
  "buyingFormat": "Best Offer",
  "itemLocation": "United Kingdom",
  "watchers": 13,
  "soldCount": null,
  "seller": { "name": "reece_c94", "feedbackPercent": 100, "feedbackScore": 136 },
  "brand": "Apple",
  "mpn": "MRW13LL/A",
  "specifics": { "Brand": "Apple", "Screen Size": "16 in", "Processor": "M3 Pro" },
  "images": ["https://i.ebayimg.com/images/g/VxsAAeSwYelqaEGZ/s-l1600.webp"],
  "variations": [],
  "returnPolicy": "30 days returns. Buyer pays for return shipping.",
  "availability": "More than 10 available",
  "descriptionText": "…",
  "isSponsored": true,
  "scrapedAt": "2026-08-18T09:12:44.481Z"
}
```

Notable fields:

- **`price` / `currency`** — parsed from the marketplace's own locale format, so `1.249,99` on `ebay.de` and `1,249.99` on `ebay.com` both become `1249.99`.
- **`shipping`** — `0` for free shipping, `null` when eBay does not state a cost (never silently guessed as `0`).
- **`isSponsored`** — eBay renders a reversed, transparent decoy label on *every* card; this field resolves the real `aria-labelledby` target, so it flags only genuine paid placements.
- **`url`** — canonicalised to `https://<marketplace>/itm/<id>`, with tracking parameters stripped, so the same listing is never scraped twice.
- **`seller.feedbackScore`** — locale-aware: `4.286` on ebay.de and `143.4K` on ebay.com both resolve to a plain integer.

Requests that fail after all retries are written to a **separate `failed-requests` dataset**, keeping the main dataset free of error rows.

***

### Troubleshooting

**"Scraped 0 items — no page could be loaded"**
eBay is rejecting the IP pool. Enable Apify Proxy with the `RESIDENTIAL` group, try a different proxy country, and lower `maxConcurrency`.

**"eBay returned no listings for this input"**
The pages loaded fine and eBay genuinely had no matches, so the run **succeeds** with an empty dataset rather than failing. Check the keyword, the marketplace and the condition/listing-type filters.

**Fewer items than `maxItems`**
The search had fewer matches, `maxPagesPerQuery` capped the crawl, or some listings ended mid-run. Raise `maxPagesPerQuery` if the search itself had more results.

**"…markup may have changed"**
eBay altered its HTML. Re-run with `debugSnapshots: true`; the offending page is stored in the key-value store, which is exactly what a bug report needs.

**A field I set doesn't seem to be applied**
An out-of-range number or an unrecognized value (a typo in `sortBy`, `crawlerMode`, `ebaySite`, etc.) does not fail the run — it falls back to that field's default and logs a warning explaining what was ignored and why. Check the run log for a line starting with "Falling back to defaults for...".

***

### Development

```bash
npm install
npm test          # 104 unit + integration tests, no network required
apify run --purge # local run; reads storage/key_value_stores/default/INPUT.json
```

Tests run against a fixture captured from a live eBay results page, so a markup change breaks the suite rather than the production run.

### Legal

This Actor collects data that eBay publishes publicly, without logging in. You are responsible for using the scraped data in line with eBay's terms and the laws that apply to you — in particular, do not collect or process personal data without a lawful basis.

# Actor input Schema

## `search` (type: `array`):

One or more keywords to search on eBay, e.g. `macbook pro m3`. Each keyword is crawled separately. Provide this or Start URLs (or both).

## `startUrls` (type: `array`):

eBay search-results URLs or individual listing (`/itm/...`) URLs. Listing URLs are scraped directly; search URLs are paginated. Non-eBay URLs are ignored.

## `ebaySite` (type: `string`):

Regional eBay site used for keyword searches. Ignored for Start URLs, which keep their own domain.

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

Hard cap on how many products are stored in the dataset. The run stops as soon as it is reached.

## `maxPagesPerQuery` (type: `integer`):

How deep to paginate each search. eBay serves up to 240 listings per page, so 5 pages is already ~1200 listings.

## `scrapeDetails` (type: `boolean`):

Visit every listing to collect item specifics, the full image gallery, return policy, availability and description. Turn this off for a much faster and cheaper run — the search page alone already yields price, shipping, condition, seller rating, location and watch counts.

## `scrapeDescription` (type: `boolean`):

Fetch the description iframe for each listing (one extra request per item). Requires "Open each listing page".

## `includeDescriptionHtml` (type: `boolean`):

Store `descriptionHtml` alongside `descriptionText`. Descriptions can be large, so leaving this off keeps the dataset small.

## `sortBy` (type: `string`):

Sort order applied to keyword searches.

## `itemCondition` (type: `string`):

Filter keyword searches by condition.

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

Filter keyword searches by purchase format.

## `proxyConfiguration` (type: `object`):

eBay blocks datacenter traffic aggressively. Residential proxies are strongly recommended — without them most runs will be challenged.

## `crawlerMode` (type: `string`):

`Automatic` sends one probe request and picks the cheapest transport that eBay accepts for this run, falling back to a browser for anything that gets blocked. Force `HTTP only` for maximum speed or `Browser` if you are seeing persistent challenges.

## `maxConcurrency` (type: `integer`):

Upper bound on parallel requests. Lower it if eBay starts challenging the run; the Actor also scales down automatically when memory is tight.

## `maxRequestRetries` (type: `integer`):

How many times a blocked or failed request is retried (each retry uses a fresh proxy IP and browser fingerprint).

## `debugSnapshots` (type: `boolean`):

When a page loads but nothing can be extracted, store its HTML in the key-value store for debugging. Useful when reporting that eBay changed its markup.

## Actor input object example

```json
{
  "search": [
    "macbook pro m3"
  ],
  "ebaySite": "ebay.com",
  "maxItems": 2,
  "maxPagesPerQuery": 5,
  "scrapeDetails": true,
  "scrapeDescription": true,
  "includeDescriptionHtml": false,
  "sortBy": "bestMatch",
  "itemCondition": "all",
  "listingType": "all",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "crawlerMode": "auto",
  "maxConcurrency": 10,
  "maxRequestRetries": 5,
  "debugSnapshots": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing every scraped eBay listing.

## `debugFiles` (type: `string`):

Key-value store holding page HTML captured when "Save HTML snapshots" is enabled.

# 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 = {
    "search": [
        "macbook pro m3"
    ],
    "maxItems": 2,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("incognito_mode/ebay-product-scrapper").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 = {
    "search": ["macbook pro m3"],
    "maxItems": 2,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("incognito_mode/ebay-product-scrapper").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 '{
  "search": [
    "macbook pro m3"
  ],
  "maxItems": 2,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call incognito_mode/ebay-product-scrapper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,incognito_mode/ebay-product-scrapper"
        }
    }
}

```

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/XiZsCIgjdu6U6XGLq/builds/uyv5YQDqWz4wM3wpn/openapi.json
