~ / guides / How to Scrape eBay (Python Guide)

How to Scrape eBay (Python Guide)

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • A plain requests + BeautifulSoup scraper returns HTTP 403 on eBay search and item pages, even with a full browser User-Agent. I reproduced this twice in June 2026.
  • eBay serves a 1,832-byte "Error Page | eBay" block instead of listings, so there is nothing to parse. The DIY path stalls at the first request.
  • eBay's robots.txt (version v28_COM_June_2026) prohibits robots and LLM-driven bots and points commercial users to its official API.
  • Two routes return data: the official eBay Browse API (free, needs a developer key) for catalog access, or a scraper API like ChocoData when you need the rendered public page across many listings.

eBay is a price-research goldmine: live auction and Buy It Now prices, sold comps, seller ratings, and item specifics across nearly every category. It is also one of the harder consumer sites to scrape directly. This guide builds a real Python scraper, runs it against a live eBay page, and shows exactly where it breaks. I ran the code in June 2026 and pasted the real output below, including the 403 that stopped it. Then I cover the two routes that actually return data.

You can request a public eBay page with a script, but eBay blocks plain scrapers at the door and its terms restrict automated access. The legality splits into two layers worth separating, and the technical reality (covered in the next section) is that a naive scraper does not even get past the first request.

On US computer-access law, reading pages anyone can load without logging in sits on the safer side. In hiQ Labs v. LinkedIn, the Ninth Circuit held that scraping public profiles does not violate the Computer Fraud and Abuse Act, and the Supreme Court’s Van Buren decision narrowed the CFAA toward traditional hacking. So fetching a public listing page with a bot is not, by itself, a federal computer crime.

eBay has its own landmark case, and it is about a different theory. In eBay v. Bidder’s Edge (N.D. Cal. 2000), eBay won a preliminary injunction against an auction aggregator on a trespass to chattels claim: the court found that automated querying of eBay’s servers, against eBay’s wishes, could be an actionable trespass because of the load it placed on their systems. That theory is older than hiQ and still informs how aggressive crawling of eBay is viewed. Add contract law on top: eBay’s User Agreement prohibits using bots or other automated means to access the site without permission, and its robots.txt says so directly. Stay on public pages, never script a logged-in account, avoid collecting personal data (which pulls in the GDPR in Europe), and prefer the official API for anything commercial. I am a tester, not a lawyer, so get advice before a commercial scrape. My is web scraping legal pillar covers the full picture.

What does eBay’s robots.txt actually say?

eBay’s robots.txt carries an explicit anti-automation policy and disallows the exact paths a scraper wants. I fetched it live in June 2026; it is versioned v28_COM_June_2026 and opens with a policy block, not just directives:

# The use of robots or other automated means to access the eBay site
# without the express permission of eBay is strictly prohibited.
#
# Checkouts are strictly for human users.
# * Automated scraping, buy-for-me agents, LLM-driven bots, or any
#   end-to-end flow that attempts to place orders without human review
#   is strictly prohibited.
# * Approved enterprise integrations must use our official API and
#   comply with our API License Agreement.

User-agent: *
Disallow: /*_kw
Disallow: /itm/*,
Disallow: /b/*?*_nkw
...

Two things matter here. eBay names “LLM-driven bots” and “automated scraping” specifically, which is a 2026 update aimed at agentic tooling. And the Disallow rules cover keyword search URLs (*_kw, *_nkw) and many item (/itm/) and browse (/b/) patterns, which is most of what you would target. robots.txt is not law, and ignoring it is evidence of intent in a dispute and a direct breach of the site terms that incorporate it. The practical takeaway is below: even if you ignore the file, eBay’s server blocks you anyway.

What data is available on an eBay listing or search page?

A search results page and an item page together carry most of the commercially useful fields. Here is what is on the public HTML and the typical selector, assuming you can retrieve the page (the next section shows why a plain scraper cannot):

FieldWhereTypical selector
Listing titleSearch + item.s-item__title / h1.x-item-title__mainTitle
PriceSearch + item.s-item__price / .x-price-primary
Buying format (auction / BIN)Search.s-item__purchase-options
Bids / time leftSearch + item.s-item__bids / .x-bid-count
Shipping costSearch + item.s-item__shipping
Item conditionSearch + item.s-item__subtitle / .x-item-condition
Seller name + feedback %Item.x-sellercard-atf__info__about-seller
Item specifics (brand, model)Item.ux-labels-values rows
Item IDURL/itm/{itemId}
Image URLsSearch + itemimg in .s-item__image / carousel

The descriptive fields and the price both sit in the static HTML, so parsing is not the hard part on eBay. Retrieval is. Unlike some sites where the price is gated behind a session, eBay’s challenge is getting any 200 response at all from a script.

Method 1: scrape eBay with requests and BeautifulSoup (what really happened)

A requests GET with a real browser User-Agent plus BeautifulSoup is the standard first attempt, and on eBay it fails at the request. Here is the exact code I ran against a live eBay search page:

import requests
from bs4 import BeautifulSoup

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/124.0.0.0 Safari/537.36"
    ),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Sec-Fetch-Mode": "navigate",
}

url = "https://www.ebay.com/sch/i.html?_nkw=mechanical+keyboard"
resp = requests.get(url, headers=HEADERS, timeout=20)
print("STATUS:", resp.status_code)
print("BYTES :", len(resp.content))

soup = BeautifulSoup(resp.text, "html.parser")
title = soup.find("title")
print("TITLE :", title.get_text(strip=True) if title else None)

items = soup.select(".s-item__title")
print("ITEMS :", len(items))

This is the real output I got, run twice (search page and an item URL):

STATUS: 403
BYTES : 1832
TITLE : Error Page | eBay
ITEMS : 0

eBay returned a 403 with a 1,832-byte error page titled “Error Page | eBay” instead of listings, so ITEMS is 0 because there is nothing to parse. I sent a full browser header set, including Accept-Language and Sec-Fetch-*, and still got blocked. I did not find a cookie-less requests path that returns the real page, so I am not pasting any parsed listing fields. There were none to paste.

How eBay blocks you

eBay fingerprints the whole request, not the User-Agent string. The block comes from a layered detection stack:

Matching the User-Agent solves none of these. To get past them with DIY tooling you would need a real browser engine (Playwright) plus rotating residential IPs, which is most of what a scraper API already bundles. Before going down the proxy rabbit hole, see how to scrape without getting blocked.

Method 2: the official eBay Browse API

For catalog and search data, eBay’s own API is the route its robots.txt points you to, and it is free. The Browse API (part of eBay’s Buy APIs) returns item and search results as JSON. You register a free developer account, get an OAuth application token, and call a documented endpoint. The request shape looks like this:

import requests

TOKEN = "YOUR_OAUTH_APP_TOKEN"  # from developer.ebay.com, OAuth client-credentials

endpoint = "https://api.ebay.com/buy/browse/v1/item_summary/search"
headers = {
    "Authorization": f"Bearer {TOKEN}",
    "X-EBAY-C-MARKETPLACE-ID": "EBAY_US",
}
params = {"q": "mechanical keyboard", "limit": 5}

resp = requests.get(endpoint, headers=headers, params=params, timeout=30)
print("STATUS:", resp.status_code)
# resp.json()["itemSummaries"] -> title, price, condition, seller, itemWebUrl, ...

The API gives you clean, structured fields without parsing HTML or fighting blocks, and it is the lowest-legal-risk option because you are using eBay the way eBay sanctions. The trade-offs: you must accept the API License Agreement, tokens expire and need refreshing, call volume is rate-limited, and some data (full sold-history depth, certain item specifics) is restricted or behind approval-gated APIs like Marketplace Insights. I did not paste live API output here because that needs a registered token, and I will not fake numbers. The endpoint and headers above come from eBay’s developer docs.

Method 3: scrape eBay with a scraper API

When you need the rendered public page itself (a sold-listings view, a seller’s store, fields the Browse API does not expose), a scraper API gets past the 403 that stopped Method 1. It runs the request through a real browser engine and a rotating residential IP, so eBay sees a browser-shaped connection instead of a Python script.

ChocoData is the service I use for this category. It exposes a universal endpoint (one URL for any site) plus 453 dedicated endpoints for common targets. You hand it the eBay URL and it returns the rendered page. Here is the request shape, swapping the raw requests.get from Method 1 for the API:

import requests

API_KEY = "cd_live_YOUR_KEY"  # free tier: 1,000 requests/month, no card
target = "https://www.ebay.com/sch/i.html?_nkw=mechanical+keyboard"

endpoint = "https://api.chocodata.com/api/v1/universal/get"
params = {
    "api_key": API_KEY,
    "url": target,
    "render": "true",   # run a real browser engine so eBay sees a browser
    "country": "us",    # US residential IP to match the marketplace
}

resp = requests.get(endpoint, params=params, timeout=90)
print("STATUS:", resp.status_code)
# Rendered HTML comes back in the payload; parse .s-item__title / .s-item__price
# with BeautifulSoup exactly as in Method 1, but now there is a real page to parse.

The difference from Method 1 is what happens behind that one call. ChocoData fetches through a country-matched residential IP that rotates automatically and retries through a fresh IP when a block appears, and the render flag runs a browser engine so the connection is browser-shaped instead of the bare requests fingerprint eBay rejected. Per ChocoData’s docs, billing applies only to successful (2xx) responses, so the 403 problem from Method 1 does not quietly drain your budget. I did not paste live API output here because it requires a paid key, and I will not fake numbers. The honest claim is narrow: the universal endpoint takes a URL and returns the rendered page, which removes the specific failure (the 403 block) I reproduced in Method 1. I have not independently benchmarked their success rate on eBay, so treat the parsing claims as vendor-stated until you test on the free tier.

Should you use the API or scrape the page?

Use the official Browse API first; reach for a scraper API only for data the official API does not expose. The choice comes down to what you need and how eBay sanctions it.

ApproachWhat you getCostBlock riskBest for
DIY requests + BS4403, no data (my test)FreeBlocked immediatelyNothing on eBay today
Official Browse APIStructured JSON, sanctionedFree, key + rate limitsNone (you are allowed)Catalog, search, item data
Scraper APIRendered public pageFree tier, then per requestHandled for youSold pages, stores, gaps in the API

My honest take from the test: a plain Python scraper does not work on eBay in 2026, full stop, because it never gets a 200. If your data is in the Browse API, use it: it is free, structured, and the route eBay endorses. If you need the rendered public page beyond what the API gives, a scraper API is the practical way to get a real response instead of the 403 I hit.

To go deeper on the building blocks, see the Python web scraping guide for the language fundamentals, BeautifulSoup for parsing, and Scrapy if you are crawling many pages and want a framework. The web scraping pillar ties the whole workflow together.

FAQ

Does eBay have a free official API for listing data?

Yes. The eBay Browse API (part of the Buy APIs at developer.ebay.com) returns item and search data as JSON and is free to use with a developer account and OAuth token, subject to eBay's API License Agreement and call limits. It is the route eBay's own robots.txt points commercial users toward, and it avoids the 403 you get scraping the HTML.

Why does my eBay scraper get a 403 when the page opens fine in my browser?

eBay fingerprints the request, not just the User-Agent string. A real Chrome request carries a TLS handshake, HTTP/2 frame order, and header set that a plain Python requests call cannot fully imitate, so eBay returns a 403 error page to the script while serving the real page to the browser. Matching the User-Agent alone is not enough.

Can I scrape sold and completed eBay listings?

Sold-listing pages are public, but they sit behind the same anti-bot wall and return the same 403 to a plain scraper. eBay's Marketplace Insights API exposes some sold-item data to approved applications, and a scraper API can fetch the rendered sold-listings page. There is no reliable cookie-less requests path to them.

MR
Marcus Reed
I've built and run web scrapers for the better part of a decade. On this site I put scraper APIs and scraping tools through real jobs against real targets, then write up what actually holds up.