Scrape eBay Listings and Prices (Search + Pagination + CSV)

eBay search pages are a great scraping exercise: the structure is consistent, pagination is explicit, and the data is useful for price tracking and market research.

The catch is real: direct repeated requests are often blocked with 403 responses. That is why this tutorial is built around a fetch layer you can route through ProxiesAPI from day one.

We will build a scraper that:

  • fetches search results
  • extracts title, price, shipping, seller, and URL
  • paginates multiple pages
  • exports a clean CSV

eBay search results (we will scrape listing cards)

Make eBay scraping less brittle with ProxiesAPI

eBay often blocks direct, repeated requests from a single IP. Keeping a clean fetch layer (and routing it through ProxiesAPI when needed) helps you scale searches and pagination without constantly reworking your code.


URL patterns and pagination

A common eBay search URL is:

https://www.ebay.com/sch/i.html?_nkw=iphone

Pagination typically uses _pgn:

https://www.ebay.com/sch/i.html?_nkw=iphone&_pgn=2


Setup

python3 -m venv .venv
source .venv/bin/activate
pip install requests beautifulsoup4 lxml

Step 1: Fetch layer (ProxiesAPI-friendly)

This uses the common wrapper format:

http://api.proxiesapi.com/?auth_key=YOUR_KEY&url=https://target.com/...

Set PROXIESAPI_KEY in your environment to enable it.

import csv
import os
import random
import time
from urllib.parse import quote, urlencode

import requests
from bs4 import BeautifulSoup

UA_POOL = [
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36",
]

def proxiesapi_url(target_url: str) -> str:
    key = os.environ.get("PROXIESAPI_KEY")
    if not key:
        return target_url
    return f"http://api.proxiesapi.com/?auth_key={quote(key)}&url={quote(target_url, safe='')}"

def fetch(url: str, *, timeout: tuple[int, int] = (10, 30), max_retries: int = 4) -> str:
    session = requests.Session()
    last_err: Exception | None = None
    for attempt in range(1, max_retries + 1):
        try:
            final = proxiesapi_url(url)
            r = session.get(final, timeout=timeout, headers={"User-Agent": random.choice(UA_POOL)})
            r.raise_for_status()
            return r.text
        except Exception as e:
            last_err = e
            if attempt == max_retries:
                break
            time.sleep(0.8 * (2 ** (attempt - 1)) + random.random() * 0.25)
    raise last_err or RuntimeError("fetch failed")

Step 2: Parse listings

eBay search pages commonly structure results with li.s-item. Useful inner selectors:

  • link: a.s-item__link
  • title: .s-item__title
  • price: .s-item__price
  • shipping: .s-item__shipping (often present, sometimes not)
def clean_text(x: str | None) -> str | None:
    if x is None:
        return None
    t = " ".join(x.split()).strip()
    return t or None

def parse_search_results(html: str) -> list[dict]:
    soup = BeautifulSoup(html, "lxml")
    items = soup.select("li.s-item")

    out: list[dict] = []
    for it in items:
        a = it.select_one("a.s-item__link[href]")
        url = a.get("href") if a else None

        title_el = it.select_one(".s-item__title")
        title = clean_text(title_el.get_text(" ", strip=True) if title_el else None)
        if not title or title.lower() in {"shop on ebay", "results matching fewer words"}:
            continue

        price_el = it.select_one(".s-item__price")
        ship_el = it.select_one(".s-item__shipping")
        seller_el = it.select_one(".s-item__seller-info-text") or it.select_one(".s-item__seller-info")

        out.append({
            "title": title,
            "price": clean_text(price_el.get_text(" ", strip=True) if price_el else None),
            "shipping": clean_text(ship_el.get_text(" ", strip=True) if ship_el else None),
            "seller": clean_text(seller_el.get_text(" ", strip=True) if seller_el else None),
            "url": url,
        })

    return out

Step 3: Pagination and CSV export

def build_search_url(*, keyword: str, page: int) -> str:
    base = "https://www.ebay.com/sch/i.html"
    params = {"_nkw": keyword, "_pgn": str(page)}
    return f"{base}?{urlencode(params)}"

def crawl_search(keyword: str, *, pages: int = 3) -> list[dict]:
    seen: set[str] = set()
    all_rows: list[dict] = []
    for page in range(1, pages + 1):
        url = build_search_url(keyword=keyword, page=page)
        html = fetch(url)
        batch = parse_search_results(html)
        for row in batch:
            u = row.get("url") or ""
            if not u or u in seen:
                continue
            seen.add(u)
            all_rows.append(row)
        if not batch:
            break
    return all_rows

def write_csv(rows: list[dict], path: str) -> None:
    fieldnames = ["title", "price", "shipping", "seller", "url"]
    with open(path, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=fieldnames)
        w.writeheader()
        for r in rows:
            w.writerow({k: r.get(k) for k in fieldnames})

if __name__ == "__main__":
    rows = crawl_search("iphone", pages=2)
    print("rows:", len(rows))
    write_csv(rows, "ebay_search_results.csv")
    print("wrote ebay_search_results.csv")

Where ProxiesAPI fits

eBay is a site where the difference between a toy script and a useful pipeline is usually the network layer. Keep parsing/export pure, and make ProxiesAPI a switch in fetch; that lets you scale keywords and pages without repeatedly re-architecting.

Make eBay scraping less brittle with ProxiesAPI

eBay often blocks direct, repeated requests from a single IP. Keeping a clean fetch layer (and routing it through ProxiesAPI when needed) helps you scale searches and pagination without constantly reworking your code.

Related guides

Scrape eBay Listings + Sold Prices with Python (Active + Completed Listings)
Build a small eBay dataset (title, price, condition, shipping) from search results, then pull completed/sold prices from the Sold filter. Includes pagination, CSV export, and ProxiesAPI in the fetch layer.
tutorial#python#ebay#web-scraping
Python BeautifulSoup Tutorial: Scraping Your First Website (2026)
A beginner-friendly BeautifulSoup tutorial: fetch HTML with requests, parse elements with CSS selectors, handle pagination, avoid common pitfalls, and export results. Includes an honest ProxiesAPI section for when you scale.
tutorial#python beautifulsoup tutorial#python#beautifulsoup
Scrape Craigslist Listings by Category and City (Python + ProxiesAPI)
Build a Craigslist scraper with pagination + dedupe, capture title/price/location/date, export CSV, and keep the fetch layer resilient with ProxiesAPI. Includes a target-page screenshot.
tutorial#python#craigslist#web-scraping
Scrape Goodreads Book Reviews + Ratings with Python (Pagination + CSV)
Extract Goodreads community reviews (rating, review text, reviewer, date) from a book page, paginate using Goodreads’ "More reviews" cursor link, and export results to CSV. Includes screenshot and ProxiesAPI fetch-layer integration.
tutorial#python#goodreads#web-scraping