Eyal Rosenthal · Web scraping at scale

How to Scrape eBay Listings in 2026

How to Scrape eBay Listings in 2026

eBay is the easiest major e-commerce target to scrape. Unlike Amazon, the anti-bot is mild. Unlike Twitter/LinkedIn, the official API is genuinely useful. The CSS structure of search-result pages has been stable for years.

The honest verdict first

Use caseRecommendation
Active listings by keywordeBay Browse API (free, 5k req/day)
Sold listings (price history)DIY scraping (Browse API doesn't expose sold listings cleanly)
Bulk competitor pricingBrowse API + parallelization
Real-time auction monitoringDIY (poll the listing every 30s)
Build a price-tracking productCombine API + scraping for sold-listings completeness

The official Browse API

developer.ebay.com — sign up, get a Production app key, you're done. Free tier: 5,000 calls/day.

import requests

EBAY_KEY = "YOUR_KEY"

def search_listings(keyword: str, n: int = 50) -> list[dict]:
    r = requests.get("https://api.ebay.com/buy/browse/v1/item_summary/search",
        params={"q": keyword, "limit": n},
        headers={"Authorization": f"Bearer {EBAY_KEY}",
                 "X-EBAY-C-MARKETPLACE-ID": "EBAY_US"})
    return r.json().get("itemSummaries", [])

for item in search_listings("rare books", 25):
    print(item["title"], "—", item["price"]["value"], item["price"]["currency"])

Returns title, price, image URL, item ID, condition, seller, location, watch count, free-shipping flag — everything you'd extract from the HTML.

The DIY scraping path (for sold listings)

Sold-listings price history is the one thing the API doesn't expose well. For competitive pricing analysis:

import time
from random import uniform
from curl_cffi import requests as cf
from bs4 import BeautifulSoup

def ebay_sold_listings(query: str, n: int = 60) -> list[dict]:
    url = "https://www.ebay.com/sch/i.html"
    params = {
        "_nkw": query,
        "LH_Sold": "1",         # filter to sold listings
        "LH_Complete": "1",     # completed listings only
        "rt": "nc",
        "_ipg": "60",           # 60 per page
    }
    r = cf.get(url, params=params, impersonate="chrome131", timeout=20)
    if r.status_code != 200:
        return []

    soup = BeautifulSoup(r.text, "html.parser")
    results = []
    for li in soup.select("li.s-item")[:n]:
        title_el = li.select_one(".s-item__title")
        price_el = li.select_one(".s-item__price")
        sold_el = li.select_one(".s-item__title--tag, .s-item__caption")
        link_el = li.select_one("a.s-item__link")
        if not (title_el and price_el and link_el):
            continue
        results.append({
            "title": title_el.get_text(strip=True).replace("New Listing", "").strip(),
            "price": price_el.get_text(strip=True),
            "sold_date": sold_el.get_text(strip=True) if sold_el else None,
            "url": link_el["href"].split("?")[0],
        })
    return results

# Usage
sold = ebay_sold_listings("first edition Hemingway", n=60)
for s in sold:
    print(s["title"], "—", s["price"], "—", s["sold_date"])
time.sleep(uniform(2.0, 4.0))

eBay tolerates this kind of slow polling (1 request per 2-4 seconds) without challenging. At higher volume (>10 req/sec), expect rate-limiting.

Combining API + scraping for full coverage

The clean pattern for competitor-pricing or condition-trend analysis:

  1. Use the Browse API for live active listings — no scraping, no anti-bot, no maintenance.
  2. Scrape sold-listings pages for the historical data — slow, polite, refreshed monthly.
  3. Merge into a single dataset — keyed by item-title-fuzzy-match.

This gives you the price-now (API) and price-history (scraped) views of the same product type, with maximum API quota usage and minimum scraping risk.

Auction-end monitoring

For bidding-window or auction-ending alerts, the Browse API has an endingTimeFrom filter:

import datetime as dt

def ending_soon(query: str, hours: int = 6, n: int = 50) -> list[dict]:
    end = (dt.datetime.utcnow() + dt.timedelta(hours=hours)).isoformat() + "Z"
    r = requests.get("https://api.ebay.com/buy/browse/v1/item_summary/search",
        params={"q": query, "filter": f"endingTimeTo:[..{end}]", "limit": n},
        headers={"Authorization": f"Bearer {EBAY_KEY}"})
    return r.json().get("itemSummaries", [])

Combine with cron (every 30 min) and a Slack webhook for "auctions ending today matching my watch list" alerts.

Real-world demo

See portfolio_demos/competitor_watch/ in the repo for the recurring-monitor pattern. Same template works for eBay watch-list monitoring.

eBay's ToS allows API access (that's what the API is for). Direct HTML scraping sits in a grey zone — they tolerate it for reasonable-volume personal use; they pursue larger commercial scrapers with C&D letters.

Practical rules:

  1. Use the API for anything you'll deploy or commercialize
  2. Scrape sold-listings only at slow speeds (<1 req/sec)
  3. Don't republish the data as a competing search product
  4. Don't scrape user data (seller profiles, buyer-feedback histories) — that's the high-risk territory

For an eBay-data brief, send to info@luba.media. Quick fixed-price gigs ($150-500), most ship in 1-3 days using the API + selective scraping.

Hire me to build this for your site

I quote fixed-price and ship in 7-10 days. Send a brief to info@luba.media.

Send a brief