eBay is the world's largest online auction and marketplace, with over 130 million active buyers and 1.9 billion live listings at any given time. It's a goldmine for:
What makes eBay uniquely valuable for scraping is its sold listings data. Unlike Amazon where you only see current prices, eBay shows what items actually sold for — giving you real transaction data, not just listing prices. This is the #1 source for market valuation across collectibles, electronics, and resale markets.
eBay provides several APIs, but they have significant limitations for data extraction:
| API | What It Does | Limitations |
|---|---|---|
| Browse API | Search items, get details | 5,000 calls/day, no sold listings, limited filters |
| Finding API | Advanced search | Being deprecated, no item specifics in results |
| Merchandising API | Similar items, deals | Deprecated — shut down in 2023 |
| Taxonomy API | Category tree | Categories only — no product data |
| Analytics API | Seller metrics | Only your own store — can't analyze competitors |
The biggest gap: no sold/completed listing data via API. eBay's completed listing data — what items actually sold for — is arguably the most valuable data on the platform, and it's only available through web scraping. The Browse API also caps you at 5,000 calls per day, which is insufficient for monitoring large catalogs or running real-time price comparisons.
For price research, competitive analysis, or feeding data to AI agents, web scraping is the practical choice.
We'll cover four approaches, from lightweight scripting to production-ready APIs:
eBay is one of the more scraping-friendly major platforms because most product pages are server-rendered HTML. Unlike JavaScript-heavy SPAs, you can extract the majority of data with simple HTTP requests — no headless browser needed.
import requests
from bs4 import BeautifulSoup
import json
import time
import random
def scrape_ebay_search(query, max_pages=3):
"""Scrape eBay search results for a given query."""
base_url = "https://www.ebay.com/sch/i.html"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
}
all_items = []
for page in range(1, max_pages + 1):
params = {
"_nkw": query,
"_pgn": page,
"_ipg": 60, # Items per page
}
response = requests.get(base_url, params=params, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
# Find all search result items
items = soup.select("div.s-item__wrapper")
for item in items:
title_el = item.select_one("div.s-item__title span[role='heading']")
price_el = item.select_one("span.s-item__price")
link_el = item.select_one("a.s-item__link")
condition_el = item.select_one("span.SECONDARY_INFO")
shipping_el = item.select_one("span.s-item__shipping")
seller_el = item.select_one("span.s-item__seller-info-text")
bids_el = item.select_one("span.s-item__bids")
if not title_el:
continue
item_data = {
"title": title_el.get_text(strip=True),
"price": price_el.get_text(strip=True) if price_el else None,
"url": link_el["href"].split("?")[0] if link_el else None,
"condition": condition_el.get_text(strip=True) if condition_el else None,
"shipping": shipping_el.get_text(strip=True) if shipping_el else None,
"bids": bids_el.get_text(strip=True) if bids_el else None,
}
all_items.append(item_data)
print(f"Page {page}: Found {len(items)} items")
time.sleep(random.uniform(2, 5)) # Respectful delay
return all_items
# Search for iPhone 15 listings
results = scrape_ebay_search("iPhone 15 Pro Max 256GB", max_pages=2)
print(f"Total items found: {len(results)}")
print(json.dumps(results[:3], indent=2))
def scrape_ebay_product(item_url):
"""Extract detailed data from an eBay product listing."""
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
}
response = requests.get(item_url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
# Title
title = soup.select_one("h1.x-item-title__mainTitle span")
# Price (handles auction and Buy It Now)
price = soup.select_one("div.x-price-primary span.ux-textspans")
# Condition
condition = soup.select_one("span.ux-icon-text__text span.clipped") or \
soup.select_one("div.x-item-condition span.ux-textspans")
# Seller info
seller_name = soup.select_one("span.ux-seller-section__item--seller a span")
seller_feedback = soup.select_one("span.ux-seller-section__item--seller span.ux-textspans--SECONDARY")
# Item specifics (key product attributes)
specifics = {}
spec_rows = soup.select("div.ux-layout-section-evo__row")
for row in spec_rows:
label = row.select_one("span.ux-textspans--BOLD")
value = row.select_one("div.ux-layout-section-evo__col span.ux-textspans--SECONDARY")
if label and value:
specifics[label.get_text(strip=True)] = value.get_text(strip=True)
# Shipping
shipping = soup.select_one("span[data-testid='ux-labels-values__values-content'] span.ux-textspans--BOLD")
# Number of watchers/sold
watchers = soup.select_one("span.d-quantity__availability span.ux-textspans--BOLD")
return {
"title": title.get_text(strip=True) if title else None,
"price": price.get_text(strip=True) if price else None,
"condition": condition.get_text(strip=True) if condition else None,
"seller": seller_name.get_text(strip=True) if seller_name else None,
"seller_feedback": seller_feedback.get_text(strip=True) if seller_feedback else None,
"item_specifics": specifics,
"shipping": shipping.get_text(strip=True) if shipping else None,
"watchers_sold": watchers.get_text(strip=True) if watchers else None,
"url": item_url,
}
# Scrape a specific listing
product = scrape_ebay_product("https://www.ebay.com/itm/123456789")
print(json.dumps(product, indent=2))
This is the most valuable eBay scraping use case — getting actual sale prices:
def scrape_sold_listings(query, max_pages=2):
"""Scrape eBay sold/completed listings for real transaction prices."""
base_url = "https://www.ebay.com/sch/i.html"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
}
sold_items = []
for page in range(1, max_pages + 1):
params = {
"_nkw": query,
"_pgn": page,
"LH_Complete": 1, # Completed listings
"LH_Sold": 1, # Sold items only
"_ipg": 60,
}
response = requests.get(base_url, params=params, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
items = soup.select("div.s-item__wrapper")
for item in items:
title_el = item.select_one("div.s-item__title span[role='heading']")
price_el = item.select_one("span.s-item__price")
date_el = item.select_one("span.s-item__ended-date") or \
item.select_one("span.POSITIVE")
if title_el:
sold_items.append({
"title": title_el.get_text(strip=True),
"sold_price": price_el.get_text(strip=True) if price_el else None,
"sold_date": date_el.get_text(strip=True) if date_el else None,
})
time.sleep(random.uniform(2, 4))
return sold_items
# Get actual sale prices for Pokemon cards
sold = scrape_sold_listings("Charizard Base Set Holo")
for item in sold[:5]:
print(f"{item['sold_price']:12} | {item['title'][:60]}")
Mantis handles eBay's anti-bot defenses, geo-redirects, and rate limiting automatically. Get structured product data with a single API call.
View Pricing Get Started FreeWhile eBay's search results and product pages are mostly server-rendered, you'll need a headless browser for:
import asyncio
from playwright.async_api import async_playwright
import json
async def scrape_ebay_with_playwright(query, max_pages=2):
"""Scrape eBay search results using Playwright for dynamic content."""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
viewport={"width": 1920, "height": 1080},
locale="en-US",
)
page = await context.new_page()
all_items = []
for page_num in range(1, max_pages + 1):
url = f"https://www.ebay.com/sch/i.html?_nkw={query}&_pgn={page_num}"
await page.goto(url, wait_until="domcontentloaded")
# Wait for search results to load
await page.wait_for_selector("div.s-item__wrapper", timeout=10000)
# Scroll to trigger lazy loading
for _ in range(3):
await page.evaluate("window.scrollBy(0, window.innerHeight)")
await page.wait_for_timeout(1000)
# Extract items via JavaScript
items = await page.evaluate("""() => {
const items = document.querySelectorAll('div.s-item__wrapper');
return Array.from(items).map(item => ({
title: item.querySelector("div.s-item__title span[role='heading']")?.textContent?.trim(),
price: item.querySelector("span.s-item__price")?.textContent?.trim(),
url: item.querySelector("a.s-item__link")?.href?.split("?")[0],
condition: item.querySelector("span.SECONDARY_INFO")?.textContent?.trim(),
shipping: item.querySelector("span.s-item__shipping")?.textContent?.trim(),
location: item.querySelector("span.s-item__location")?.textContent?.trim(),
bids: item.querySelector("span.s-item__bids")?.textContent?.trim(),
timeLeft: item.querySelector("span.s-item__time-left")?.textContent?.trim(),
image: item.querySelector("img.s-item__image-img")?.src,
})).filter(i => i.title && i.title !== "Shop on eBay");
}""")
all_items.extend(items)
print(f"Page {page_num}: {len(items)} items")
await page.wait_for_timeout(2000) # Respectful delay
await browser.close()
return all_items
# Run the scraper
results = asyncio.run(scrape_ebay_with_playwright("vintage rolex submariner"))
print(json.dumps(results[:3], indent=2))
For JavaScript developers, Cheerio provides fast server-side HTML parsing — perfect for eBay's server-rendered pages:
const axios = require("axios");
const cheerio = require("cheerio");
async function scrapeEbaySearch(query, maxPages = 2) {
const allItems = [];
for (let page = 1; page <= maxPages; page++) {
const url = `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(query)}&_pgn=${page}&_ipg=60`;
const { data } = await axios.get(url, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
},
});
const $ = cheerio.load(data);
$("div.s-item__wrapper").each((_, el) => {
const title = $(el).find("div.s-item__title span[role='heading']").text().trim();
if (!title || title === "Shop on eBay") return;
allItems.push({
title,
price: $(el).find("span.s-item__price").text().trim() || null,
url: ($(el).find("a.s-item__link").attr("href") || "").split("?")[0],
condition: $(el).find("span.SECONDARY_INFO").text().trim() || null,
shipping: $(el).find("span.s-item__shipping").text().trim() || null,
bids: $(el).find("span.s-item__bids").text().trim() || null,
});
});
console.log(`Page ${page}: ${allItems.length} total items`);
await new Promise(r => setTimeout(r, 2500)); // Rate limiting
}
return allItems;
}
// Scrape sold listings (Node.js)
async function scrapeSoldListings(query) {
const url = `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(query)}&LH_Complete=1&LH_Sold=1&_ipg=60`;
const { data } = await axios.get(url, {
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" },
});
const $ = cheerio.load(data);
const soldItems = [];
$("div.s-item__wrapper").each((_, el) => {
const title = $(el).find("div.s-item__title span[role='heading']").text().trim();
if (!title || title === "Shop on eBay") return;
soldItems.push({
title,
soldPrice: $(el).find("span.s-item__price").text().trim(),
soldDate: $(el).find("span.POSITIVE").text().trim() || null,
});
});
return soldItems;
}
// Usage
scrapeEbaySearch("RTX 4090").then(items => {
console.log(`Found ${items.length} listings`);
console.log(JSON.stringify(items.slice(0, 3), null, 2));
});
For production applications — especially AI agents that need reliable, structured data — Mantis provides a single API call that handles eBay's anti-bot measures, geo-routing, and data extraction automatically:
import requests
# Scrape eBay product data with Mantis
response = requests.post(
"https://api.mantisapi.com/v1/scrape",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"url": "https://www.ebay.com/sch/i.html?_nkw=RTX+4090&LH_Sold=1&LH_Complete=1",
"extract": {
"items": [{
"title": "string",
"sold_price": "number",
"condition": "string",
"sold_date": "string",
}]
},
"render_js": false, # eBay is mostly server-rendered
}
)
data = response.json()
print(json.dumps(data, indent=2))
Scrape eBay products, prices, and sold listings with structured data extraction. No credit card required.
View Pricing Get Started FreeeBay's anti-bot measures are moderate compared to platforms like Facebook or TikTok, but they'll still block naive scrapers:
| Defense | Severity | Details |
|---|---|---|
| Rate limiting | 🟡 Medium | Aggressive throttling after ~50-100 requests per minute from a single IP |
| CAPTCHA challenges | 🟡 Medium | Image CAPTCHAs triggered by rapid requests or suspicious patterns |
| Geo-redirects | 🟠 Annoying | eBay redirects to local domains based on IP geolocation (ebay.co.uk, ebay.de, etc.) |
| Session cookies | 🟡 Medium | Missing or stale session cookies trigger additional verification |
| IP blocking | 🟢 Low | Datacenter IPs get blocked after sustained scraping; residential proxies mostly work fine |
| User-Agent checking | 🟢 Low | Missing or bot-like User-Agents get blocked immediately |
| Dynamic selectors | 🟢 Low | eBay's CSS classes are relatively stable compared to Meta/TikTok |
eBay's most annoying anti-scraping measure isn't technical — it's geographic. If you use a UK proxy, you get redirected to ebay.co.uk with different listings and prices. Solutions:
Accept-Language: en-US headers_sacat=0 parameter to force the US marketplace| Data Type | Available | Notes |
|---|---|---|
| Product title & description | ✅ | Full title, item description, and subtitle |
| Current price / Buy It Now | ✅ | Fixed price and auction current bid |
| Sold/completed prices | ✅ | Actual transaction prices (LH_Sold=1 parameter) |
| Item condition | ✅ | New, Used, Refurbished, For Parts, etc. |
| Item specifics | ✅ | Brand, model, color, size, material — varies by category |
| Seller profile | ✅ | Username, feedback score, feedback percentage, member since |
| Seller inventory | ✅ | All active listings from a specific seller |
| Shipping details | ✅ | Cost, service, estimated delivery, free shipping flag |
| Images | ✅ | All product images (usually 12+) |
| Bid history | ✅ | Number of bids, bid amounts (for auctions) |
| Watchers count | ✅ | "X watchers" — indicates demand |
| Category & breadcrumbs | ✅ | Full category hierarchy |
| Reviews | ✅ | Product reviews (when available on catalog items) |
| Return policy | ✅ | Return window, who pays return shipping |
Track prices for specific products and get alerts when prices drop below a threshold:
import requests
from bs4 import BeautifulSoup
import re
import json
class EbayPriceMonitor:
"""Monitor eBay prices and alert on drops."""
def __init__(self, watchlist):
"""
watchlist: list of dicts with 'query', 'max_price', 'condition'
"""
self.watchlist = watchlist
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
}
def parse_price(self, price_str):
"""Extract numeric price from eBay price string."""
if not price_str:
return None
match = re.search(r'[\d,]+\.?\d*', price_str.replace(",", ""))
return float(match.group()) if match else None
def check_prices(self):
"""Check all watchlist items for price drops."""
alerts = []
for item in self.watchlist:
params = {
"_nkw": item["query"],
"_sop": 15, # Sort by price + shipping lowest first
"LH_BIN": 1, # Buy It Now only
"_ipg": 20,
}
if item.get("condition") == "new":
params["LH_ItemCondition"] = 1000 # New items only
response = requests.get(
"https://www.ebay.com/sch/i.html",
params=params,
headers=self.headers,
)
soup = BeautifulSoup(response.text, "html.parser")
listings = soup.select("div.s-item__wrapper")
for listing in listings[:5]: # Check top 5 cheapest
price_el = listing.select_one("span.s-item__price")
price = self.parse_price(price_el.get_text() if price_el else None)
if price and price <= item["max_price"]:
title = listing.select_one("div.s-item__title span[role='heading']")
url = listing.select_one("a.s-item__link")
alerts.append({
"query": item["query"],
"title": title.get_text(strip=True) if title else "Unknown",
"price": price,
"target": item["max_price"],
"url": url["href"].split("?")[0] if url else None,
})
return alerts
# Usage: monitor GPU and console prices
monitor = EbayPriceMonitor([
{"query": "RTX 4090 new", "max_price": 1500, "condition": "new"},
{"query": "PS5 Pro console", "max_price": 600, "condition": "new"},
{"query": "MacBook Pro M3 Max", "max_price": 2800},
])
alerts = monitor.check_prices()
for alert in alerts:
print(f"🚨 DEAL: {alert['title'][:50]} — ${alert['price']} (target: ${alert['target']})")
Monitor competitor sellers' inventory, pricing, and new listings:
import requests
from bs4 import BeautifulSoup
import json
def scrape_seller_inventory(seller_id, max_pages=3):
"""Scrape all active listings from a specific eBay seller."""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
}
inventory = []
for page in range(1, max_pages + 1):
url = f"https://www.ebay.com/sch/{seller_id}/m.html?_pgn={page}&_ipg=60"
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
items = soup.select("div.s-item__wrapper")
for item in items:
title_el = item.select_one("div.s-item__title span[role='heading']")
price_el = item.select_one("span.s-item__price")
sold_count = item.select_one("span.s-item__hotness")
if title_el:
inventory.append({
"title": title_el.get_text(strip=True),
"price": price_el.get_text(strip=True) if price_el else None,
"hot": sold_count.get_text(strip=True) if sold_count else None,
})
return {
"seller": seller_id,
"total_listings": len(inventory),
"items": inventory,
}
# Track a competitor seller
competitor = scrape_seller_inventory("example_seller_123")
print(f"Seller {competitor['seller']} has {competitor['total_listings']} active listings")
Build an AI agent tool that finds the best deals by comparing active prices to recent sold prices:
import requests
from bs4 import BeautifulSoup
import re
import statistics
class EbayDealFinder:
"""AI agent tool: find underpriced eBay listings based on sold data."""
def __init__(self):
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
}
def parse_price(self, text):
if not text:
return None
match = re.search(r'[\d,]+\.?\d*', text.replace(",", ""))
return float(match.group()) if match else None
def get_market_value(self, query):
"""Get average sold price for an item (market value)."""
params = {
"_nkw": query,
"LH_Complete": 1,
"LH_Sold": 1,
"_ipg": 60,
}
response = requests.get("https://www.ebay.com/sch/i.html", params=params, headers=self.headers)
soup = BeautifulSoup(response.text, "html.parser")
prices = []
for item in soup.select("div.s-item__wrapper"):
price_el = item.select_one("span.s-item__price")
price = self.parse_price(price_el.get_text() if price_el else None)
if price:
prices.append(price)
if not prices:
return None
return {
"median": statistics.median(prices),
"mean": statistics.mean(prices),
"low": min(prices),
"high": max(prices),
"sample_size": len(prices),
}
def find_deals(self, query, discount_pct=20):
"""Find active listings priced below market value."""
market = self.get_market_value(query)
if not market:
return {"error": "No sold data found"}
threshold = market["median"] * (1 - discount_pct / 100)
# Now search active listings
params = {
"_nkw": query,
"_sop": 15, # Price + shipping lowest first
"LH_BIN": 1, # Buy It Now only
"_ipg": 60,
}
response = requests.get("https://www.ebay.com/sch/i.html", params=params, headers=self.headers)
soup = BeautifulSoup(response.text, "html.parser")
deals = []
for item in soup.select("div.s-item__wrapper"):
price_el = item.select_one("span.s-item__price")
price = self.parse_price(price_el.get_text() if price_el else None)
if price and price <= threshold:
title_el = item.select_one("div.s-item__title span[role='heading']")
url_el = item.select_one("a.s-item__link")
deals.append({
"title": title_el.get_text(strip=True) if title_el else "Unknown",
"price": price,
"market_median": market["median"],
"savings_pct": round((1 - price / market["median"]) * 100, 1),
"url": url_el["href"].split("?")[0] if url_el else None,
})
return {
"query": query,
"market_value": market,
"threshold": threshold,
"deals_found": len(deals),
"deals": deals,
}
# Usage: find underpriced listings
finder = EbayDealFinder()
deals = finder.find_deals("Nintendo Switch OLED", discount_pct=25)
print(f"Market median: ${deals['market_value']['median']:.2f}")
print(f"Found {deals['deals_found']} deals below ${deals['threshold']:.2f}")
| Feature | eBay Browse API | DIY Scraping | Mantis API |
|---|---|---|---|
| Active listings | ✅ (5K/day limit) | ✅ Unlimited | ✅ Unlimited |
| Sold/completed prices | ❌ Not available | ✅ | ✅ |
| Item specifics | ⚠️ Limited | ✅ All fields | ✅ All fields |
| Seller inventory | ❌ Not available | ✅ | ✅ |
| Bid history | ❌ | ✅ | ✅ |
| Rate limits | 5,000 calls/day | Self-managed | Plan-based |
| Anti-bot handling | N/A (official) | DIY proxies/stealth | ✅ Built-in |
| Geo-routing | Limited | DIY proxy routing | ✅ Automatic |
| Setup time | Hours (OAuth, app registration) | Hours-days | Minutes |
| Maintenance | Low (API stable) | High (selectors break) | None |
| Cost | Free (within limits) | Proxy + server costs | From $29/mo |
| Best for | Small-scale, own store | Custom requirements | Production, AI agents |
This was one of the first web scraping court cases and established the "trespass to chattels" doctrine for automated data collection. eBay sued Bidder's Edge, an auction aggregator that scraped listings from multiple auction sites. The court granted a preliminary injunction, ruling that Bidder's Edge's automated crawling constituted trespass to eBay's servers.
However, this case predates modern scraping law and has been partially superseded by more recent rulings:
eBay's ToS explicitly prohibit automated data collection:
"You will not use any robot, spider, scraper, data mining tools, data gathering and extraction tools, or other automated means to access our Services for any purpose."
ToS violations are a breach of contract, not a criminal matter. The enforceability varies by jurisdiction and the nature of the data collected.
See the FAQ schema above for the six most common questions about scraping eBay. These target featured snippets in Google search results.
Join developers using Mantis to scrape eBay products, track prices, and feed data to AI agents — without managing proxies or infrastructure.
View Pricing Get Started Free