How to Scrape eBay: Complete Guide for 2026
Learn how to scrape eBay listings, prices, and seller data in 2026. Bypass Akamai Bot Manager, handle JS rendering, and extract structured data at scale.
March 23, 2026
eBay hosts over 1.7 billion live listings across every product category, with prices and availability shifting by the hour. The sold listing data alone — actual transaction prices, not asking prices — is one of the most useful secondary-market benchmarks available on the public web. This guide covers everything you need to extract that data reliably in 2026: what protections you're up against, how to get structured data out of both search and item pages, and how to scale without burning through proxies.
Why Scrape eBay?
Three use cases dominate production eBay pipelines:
Price monitoring and repricing — Resellers and retailers track eBay prices to benchmark against their own inventory and set dynamic pricing rules. A GPU listed at $650 new on a retailer's site might clear at $390 on eBay's secondary market; that spread is actionable data for sourcing, pricing, and promotion decisions.
Market research on completed listings — Appending &LH_Complete=1&LH_Sold=1 to any eBay search URL filters to sold transactions. For commodities like trading cards, vintage electronics, or rare apparel, this is the closest real-world proxy to market value — what buyers actually paid, not what sellers hoped for.
Catalog and inventory enrichment — Product data teams pull titles, condition grades, image URLs, seller ratings, and category breadcrumbs to enrich internal databases or build price comparison engines, without maintaining their own taxonomy from scratch.
Anti-Bot Challenges on eBay
eBay's protection stack is more layered than most e-commerce sites. Understanding what you're up against saves hours of debugging failed or empty responses.
Akamai Bot Manager is the primary layer. It performs TLS fingerprinting — inspecting cipher suites and extension order during the TLS handshake — to identify non-browser clients. Python's requests library produces a fingerprint Akamai's models have catalogued and block by default. This is why a raw requests.get() against an eBay search URL often returns a CAPTCHA page or a redirect rather than listings.
JavaScript-dependent rendering affects both search and item pages. Search results load listing data through client-side JavaScript calls; a static HTTP request frequently returns empty .s-item container divs. Item pages may deliver base HTML but fetch pricing and availability data via XHR that only fires inside a real browser context.
Behavioral scoring evaluates request timing, browsing sequence, and session patterns. Scrapers that hit sequential paginated URLs at uniform intervals without session continuity get scored as bots and face increasing friction — soft blocks, CAPTCHA injections, or silently degraded responses that look like valid HTML but contain no useful data.
IP reputation closes the loop. Datacenter IPs are blocked at the edge for most eBay endpoints. Residential proxies work but get burned fast at volume without smart rotation.
The AlterLab Anti-Bot Bypass API handles all of this transparently — TLS fingerprinting is matched to a real browser profile, JavaScript executes in a managed headless session, and requests route through a rotating residential pool. You send a URL and get back fully-rendered HTML.
Quick Start with AlterLab API
Install the SDK and BeautifulSoup, then grab your API key from the dashboard. The getting started guide covers account creation and your first request in under five minutes.
pip install alterlab beautifulsoup4A minimal eBay search scrape with JavaScript rendering enabled:
import alterlab
from bs4 import BeautifulSoup
client = alterlab.Client("YOUR_API_KEY")
response = client.scrape(
"https://www.ebay.com/sch/i.html?_nkw=mechanical+keyboard&_sop=12",
render_js=True,
)
soup = BeautifulSoup(response.text, "html.parser")
listings = soup.select(".s-item")
for item in listings[1:]: # eBay injects a ghost template item at index 0 — always skip it
title = item.select_one(".s-item__title")
price = item.select_one(".s-item__price")
link = item.select_one(".s-item__link")
condition = item.select_one(".s-item__subtitle")
print({
"title": title.text.strip() if title else None,
"price": price.text.strip() if price else None,
"url": link["href"] if link else None,
"condition": condition.text.strip() if condition else None,
})The same request via cURL — useful for inspecting raw response structure before writing a parser:
curl -X POST https://api.alterlab.io/v1/scrape \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.ebay.com/sch/i.html?_nkw=mechanical+keyboard",
"render_js": true
}'Try scraping an eBay search results page live with AlterLab
Extracting Structured Data
Search Results: CSS Selectors
eBay's search result pages use a consistent DOM structure. These selectors are stable as of Q1 2026:
| Field | CSS Selector |
|---|---|
| Listing container | .s-item |
| Title | .s-item__title |
| Price | .s-item__price |
| Condition | .s-item__subtitle |
| Shipping cost | .s-item__shipping |
| Item URL | .s-item__link[href] |
| Thumbnail image | .s-item__image-img[src] |
For sold listing history, append &LH_Complete=1&LH_Sold=1 to your search URL. The DOM structure is identical to active listings.
Item Detail Pages: Prefer JSON-LD
eBay embeds application/ld+json structured data on item pages. Parsing this is more reliable than CSS selectors because it survives UI redesigns and localisation changes:
import json
import alterlab
from bs4 import BeautifulSoup
client = alterlab.Client("YOUR_API_KEY")
def scrape_ebay_item(item_id: str) -> dict:
url = f"https://www.ebay.com/itm/{item_id}"
response = client.scrape(url, render_js=True)
soup = BeautifulSoup(response.text, "html.parser")
# Prefer JSON-LD — encodes price, condition, availability, and seller in one block
json_ld = soup.find("script", {"type": "application/ld+json"})
if json_ld:
data = json.loads(json_ld.string)
offers = data.get("offers", {})
return {
"name": data.get("name"),
"price": offers.get("price"),
"currency": offers.get("priceCurrency"),
"availability": offers.get("availability"),
"condition": offers.get("itemCondition"),
"seller": offers.get("seller", {}).get("name"),
}
# Fallback: CSS selectors when JSON-LD is absent (happens on some auction pages)
title_el = soup.select_one("h1.x-item-title__mainTitle span")
price_el = soup.select_one(".x-price-primary .ux-textspans")
return {
"name": title_el.text.strip() if title_el else None,
"price": price_el.text.strip() if price_el else None,
}
if __name__ == "__main__":
print(scrape_ebay_item("315012345678"))JSON-LD is present on most Buy It Now item pages but can be absent on some auction-format listings. Always implement the CSS selector fallback.
Common Pitfalls
The ghost first listing — eBay's search DOM always includes a promotional or template item at listings[0] that doesn't represent a real result. Slice from index 1 (soup.select(".s-item")[1:]) without exception.
Missing render_js=True — This is the most common cause of blank results on eBay. The .s-item containers render empty without JavaScript execution. If your output is an empty list, render_js is almost always the culprit before anything else.
Pagination model varies by category — Traditional eBay search paginates via _pgn=2, _pgn=3. But some category landing pages have migrated to infinite scroll. Before building a paginator, verify that a "Next page" element (a.pagination__next) exists in your rendered HTML.
Description iframes — Item descriptions frequently live inside an <iframe id="desc_ifr"> pointing to ebaydesc.com. If you need full item descriptions, extract the src attribute and make a second request for that URL. The iframe content will not be present in the parent page response.
Uniform timing patterns — Requests at perfectly even intervals are a bot signal. If you're managing request scheduling yourself (rather than using the batch API), introduce a small randomised delay between requests.
Scaling Up
For paginated bulk collection, the batch API handles concurrency and retries at the request level:
import alterlab
from bs4 import BeautifulSoup
client = alterlab.Client("YOUR_API_KEY")
# Five pages of search results for a single keyword
urls = [
f"https://www.ebay.com/sch/i.html?_nkw=rtx+4080&_pgn={page}&_sop=12"
for page in range(1, 6)
]
# Batch up to 50 URLs per call; concurrency controls parallel headless sessions
results = client.batch_scrape(urls, render_js=True, concurrency=5)
all_listings = []
for result in results:
if result.status_code != 200:
continue
soup = BeautifulSoup(result.text, "html.parser")
for item in soup.select(".s-item")[1:]:
title = item.select_one(".s-item__title")
price = item.select_one(".s-item__price")
link = item.select_one(".s-item__link")
if title and price:
all_listings.append({
"title": title.text.strip(),
"price": price.text.strip(),
"url": link["href"] if link else None,
})
print(f"Collected {len(all_listings)} listings across {len(urls)} pages")Cost planning — Credit consumption depends on request volume and render mode. Headless browser sessions cost more than plain fetches, so it pays to disable render_js on any pages where you've confirmed the target data exists in static HTML. Check AlterLab pricing to model costs for your pipeline before scheduling a production run.
Key Takeaways
- eBay uses Akamai Bot Manager. Standard HTTP clients get blocked at the TLS layer before a single HTML byte is returned.
- Always pass
render_js=Truefor search and item pages unless you've explicitly verified the content is in the static HTML response. - Prefer
application/ld+jsonon item pages over CSS selectors — it's more stable across redesigns and surfaces price, condition, availability, and seller in a single parse. - Always skip
listings[0]on search pages — it's a ghost/template item injected by eBay's frontend. - For actual transaction data rather than asking prices, append
&LH_Complete=1&LH_Sold=1to any search URL. - Keep batch concurrency between 5 and 10 for eBay specifically. More parallel sessions past that threshold doesn't increase throughput and burns through proxy pool capacity.
Related Guides
Building a multi-platform price intelligence pipeline? These guides cover the other major marketplaces:
- How to Scrape Amazon — Amazon's AWS WAF and dynamic ASIN pages require different anti-bot handling than eBay.
- How to Scrape Walmart — Walmart.com runs Cloudflare and has a stricter per-IP rate limit profile.
- How to Scrape AliExpress — Heavy JavaScript rendering, geolocation redirects, and currency localisation add significant complexity.
Was this article helpful?
Frequently Asked Questions
Related Articles
Popular Posts
Recommended
Selenium Bot Detection: Why You Get Caught and How to Avoid It
Why Your Headless Browser Gets Detected (and How to Fix It)
Web Scraping APIs vs DIY Scrapers: When to Stop Building Infrastructure
Scraping E-Commerce Sites at Scale Without Getting Blocked
Web Scraping with Node.js and Puppeteer: The Complete 2026 Guide
Newsletter
Scraping insights and API tips. No spam.
Recommended Reading
Selenium Bot Detection: Why You Get Caught and How to Avoid It
Why Your Headless Browser Gets Detected (and How to Fix It)
Web Scraping APIs vs DIY Scrapers: When to Stop Building Infrastructure
Scraping E-Commerce Sites at Scale Without Getting Blocked
Web Scraping with Node.js and Puppeteer: The Complete 2026 Guide
Stay in the Loop
Get scraping insights, API tips, and platform updates. No spam — we only send when we have something worth reading.