How to scrape ebay with python cover 1 2

Esta guía muestra cómo hacer scraping de eBay con Python de principio a fin: un scraper funcional para resultados de búsqueda, listados individuales, variantes de producto y reseñas, además de la configuración anti-bot y de proxies que lo mantiene en marcha. Necesitas Python 3, unas cuantas librerías y unos 30 minutos para la versión básica. Al terminar tendrás funciones reutilizables de web scraping de eBay que exportan JSON y CSV limpios.

Soy Andrii Byzov, CMO fraccional AI-Native que gestiona pipelines de datos de e-commerce. eBay es un objetivo valioso pero bien defendido, así que este recorrido sobre cómo hacer scraping de eBay combina el código con las partes que la mayoría de guías omite: paginación, datos ocultos de variantes y cómo evitar bloqueos. La versión corta de hacer scraping de eBay con seguridad: envía cabeceras de navegador reales, rota proxies residenciales y respeta los límites de tasa. Para saber qué IPs usar, consulta nuestra guía de mejores proxies para scraping de eBay.


Datos clave

  • eBay tiene dos tipos de página —resultados de búsqueda y listados de producto— con estructuras HTML distintas, así que escribes dos scrapers.
  • Las variantes (talla, color, almacenamiento) están ocultas en JSON inline, no en el HTML visible: las extraes con una expresión regular y json.loads().
  • Las cabeceras de navegador reales son lo primero: sin un User-Agent adecuado, eBay suele devolver 403 o páginas vacías incluso en peticiones simples.
  • Las IPs de centro de datos se marcan rápido en eBay; los proxies residenciales rotativos sostienen el volumen y devuelven precios correctos por región.
  • Los listados difieren por país —eBay.com, eBay.co.uk y eBay.de muestran precios y envíos distintos— así que la segmentación geográfica importa para un scraping de datos de eBay preciso.

Paso 1 — Configura tu entorno

Crea un entorno aislado e instala las librerías que necesita tu setup de web scraping de eBay. Cada una tiene su función: requests obtiene las páginas, beautifulsoup4 + lxml parsean el HTML y httpx gestiona peticiones asíncronas cuando escalas.



# Create an isolated project folder and virtual environment
mkdir ebay-scraper && cd ebay-scraper
python3 -m venv env
source env/bin/activate        # Windows: env\Scripts\activate

# requests  -> send HTTP requests
# beautifulsoup4 -> parse the returned HTML
# lxml -> fast HTML parser backend for BeautifulSoup
# httpx -> async requests for scaling later
pip install requests beautifulsoup4 lxml httpx










Paso 2 — Entiende la estructura de páginas de eBay

Antes de escribir código, entiende que eBay tiene dos tipos de página fundamentalmente distintos, y que saber cómo hacer scraping de eBay de forma fiable empieza por conocer qué selectores pertenecen a cada uno. Cada tipo de página necesita selectores distintos. Abre cualquier página, pulsa F12 (o clic derecho → Inspeccionar) y pasa el cursor sobre un elemento para ver su clase. El mapa de abajo es lo que usarás en los Pasos 3 y 4.

Página de resultados de búsqueda

Cada resultado va en un contenedor li.s-item, con el título, el precio y el enlace anidados dentro. El parámetro de URL _pgn controla la paginación: lo necesitarás en el Paso 3.

Página de listado de producto

Un listado individual expone el precio en .x-price-primary, el título en h1 span y las especificaciones del artículo en filas .ux-labels-values. La descripción larga vive en un iframe#desc_ifr aparte: una subpágina que scrapearías con su propia petición.

Dato Selector en resultados Selector en listado
Contenedor li.s-item
Título .s-item__title h1 span
Precio .s-item__price .x-price-primary
Enlace / imagen a.s-item__link / .s-item__image img
Especificaciones .ux-labels-values
Descripción iframe#desc_ifr

Paso 3 — Scrapea los resultados de búsqueda de eBay

El primer scraper toma una consulta de búsqueda y devuelve una lista de artículos con título, precio, URL e imagen: el punto de entrada de la mayoría de proyectos de web scraping de eBay. Sin la cabecera User-Agent correcta, eBay devuelve un 403 o una página vacía incluso en esta petición simple, así que las cabeceras entran desde el principio. Puedes adaptar el mismo patrón para scrapear listados de eBay desde cualquier término de búsqueda.



import requests
from bs4 import BeautifulSoup
from urllib.parse import quote_plus

HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                         "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
           "Accept-Language": "en-US,en;q=0.9"}

def scrape_search(query, proxies=None):
    """Return a list of eBay search results for a query."""
    url = f"https://www.ebay.com/sch/i.html?_nkw={quote_plus(query)}"
    html = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
    soup = BeautifulSoup(html.text, "lxml")
    items = []
    for card in soup.select("li.s-item"):
        title = card.select_one(".s-item__title")
        price = card.select_one(".s-item__price")
        link  = card.select_one("a.s-item__link")
        image = card.select_one(".s-item__image-wrapper img")
        if not (title and link):
            continue
        items.append({
            "title": title.get_text(strip=True),
            "price": price.get_text(strip=True) if price else None,
            "url":   link["href"],
            "image": image.get("src") if image else None,
        })
    return items

print(scrape_search("smartphone")[:3])






























Gestionar la paginación

Una sola página de resultados no basta. Este bucle recorre el parámetro _pgn (página 2, 3… N) y se detiene cuando la lista de resultados está vacía o alcanzas un límite de páginas: la parte que la mayoría de tutoriales de eBay scraper en Python omite.



from urllib.parse import quote_plus

def scrape_all_pages(query, max_pages=10, proxies=None):
    """Page through results via the _pgn parameter until empty or max_pages."""
    all_items = []
    for page in range(1, max_pages + 1):
        url = (f"https://www.ebay.com/sch/i.html?_nkw={quote_plus(query)}"
               f"&_pgn={page}")
        html = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
        soup = BeautifulSoup(html.text, "lxml")
        cards = soup.select("li.s-item")
        if not cards:                 # no more results -> stop
            break
        for card in cards:
            title = card.select_one(".s-item__title")
            link  = card.select_one("a.s-item__link")
            if title and link:
                all_items.append({"title": title.get_text(strip=True),
                                  "url": link["href"]})
    return all_items




















Paso 4 — Scrapea las páginas de listado de producto

Cuando scrapeas listados de eBay uno a uno, parse_product(url) toma la URL de un listado y devuelve los campos estructurados: título, precio, estado, vendedor, envío y la tabla de especificaciones donde cada fila es un par clave/valor. Es el núcleo del scraping de datos de eBay a nivel de producto.



def parse_product(url, proxies=None):
    """Scrape a single eBay listing page."""
    html = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
    soup = BeautifulSoup(html.text, "lxml")

    def text(sel):
        el = soup.select_one(sel)
        return el.get_text(strip=True) if el else None

    # item specifics live in a label/value table
    specifics = {}
    for row in soup.select(".ux-labels-values"):
        label = row.select_one(".ux-labels-values__labels")
        value = row.select_one(".ux-labels-values__values")
        if label and value:
            specifics[label.get_text(strip=True)] = value.get_text(strip=True)

    return {
        "title":     text("h1 span"),
        "price":     text(".x-price-primary"),
        "condition": text(".x-item-condition-text .ux-textspans"),
        "seller":    text(".x-sellercard-atf__info__about-seller"),
        "shipping":  text(".ux-labels-values--shipping .ux-textspans"),
        "item_specifics": specifics,
    }

























Extraer variantes de producto (datos MSKU)

eBay guarda las opciones de variante —color, talla, almacenamiento— no en el HTML visible, sino dentro de un objeto JSON inline (MSKU) en una etiqueta <script>. Localizas ese JSON y lo parseas con json.loads(). Su clave y forma exactas cambian con el tiempo, así que toma el código de abajo como punto de partida y ajústalo a lo que veas en DevTools: devuelve las opciones de variante (nombre/valor), la parte que la mayoría de competidores omite.



import re, json

def parse_variants(url, proxies=None):
    """eBay keeps multi-SKU variant options (size, colour, storage) in an inline
    JSON blob rather than visible HTML. The exact key and shape change over time,
    so treat the pattern below as a starting point and adjust it to the page you
    inspect in DevTools. This returns the option name/value pairs, not full
    price-per-combination data."""
    html = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30).text
    # find the inline state object, then load it with a balanced-brace slice
    start = html.find('"MSKU"')
    if start == -1:
        return []
    brace = html.find("{", start)
    depth, end = 0, brace
    for i in range(brace, len(html)):
        depth += (html[i] == "{") - (html[i] == "}")
        if depth == 0:
            end = i + 1
            break
    try:
        data = json.loads(html[brace:end])
    except json.JSONDecodeError:
        return []
    return [{"name": v.get("displayName"), "value": v.get("valueName")}
            for v in data.get("menuItemMap", {}).values()]


























Paso 5 — Scrapea las reseñas de producto de eBay

Al scrapear reseñas de eBay, ten en cuenta que son reseñas de producto/catálogo en una URL aparte, no en cada página de listado, y que no todos los listados las tienen. Derivas la URL de reseñas del ID de producto/catálogo del listado y luego extraes valoración, texto, fecha y autor. Toma el endpoint de abajo como ilustrativo y confirma la ruta actual en DevTools.



def scrape_reviews(item_id, proxies=None):
    """Reviews live on a separate URL keyed by the item ID."""
    url = f"https://www.ebay.com/urw/{item_id}/product-reviews"
    html = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
    soup = BeautifulSoup(html.text, "lxml")
    reviews = []
    for r in soup.select(".ebay-review-section"):
        rating = r.select_one(".star-rating")
        body   = r.select_one(".review-item-content")
        date   = r.select_one(".review-item-date")
        author = r.select_one(".review-item-author")
        reviews.append({
            "rating": rating.get("aria-label") if rating else None,
            "text":   body.get_text(strip=True) if body else None,
            "date":   date.get_text(strip=True) if date else None,
            "author": author.get_text(strip=True) if author else None,
        })
    return reviews


















Paso 6 — Exporta los datos a JSON y CSV

Guarda tus resultados en ambos formatos a la vez: JSON conserva la estructura anidada (útil para variantes y especificaciones), CSV es plano y se abre directamente en Excel o Google Sheets.



import json, csv

results = scrape_search("smartphone")

# Option A - JSON (keeps nested structure)
with open("ebay.json", "w", encoding="utf-8") as f:
    json.dump(results, f, indent=2, ensure_ascii=False)

# Option B - CSV (flat, opens in Excel/Sheets)
if results:
    with open("ebay.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=results[0].keys())
        writer.writeheader()
        writer.writerows(results)














Paso 7 — Sortea las medidas anti-bot de eBay

eBay defiende los listados con un stack en capas: puntuación de reputación de IP, límites de tasa (HTTP 429), bloqueos suaves (una respuesta 200 con resultado vacío) y CAPTCHAs. No lo vencerás todo con un truco, pero las cabeceras e IPs correctas resuelven los casos comunes para el web scraping de eBay a una escala razonable.

Configura cabeceras adecuadas

Envía un conjunto completo y realista de cabeceras, no solo un User-Agent. Cada cabecera reduce tu riesgo de bloqueo: la falta de Accept-Language o Referer es una señal típica de bot. Para trabajos mayores, rota el User-Agent de una lista.



HEADERS = {
    # identify as a real desktop browser - eBay returns 403/empty without this
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
    "Accept-Language": "en-US,en;q=0.9",      # match the locale you target
    "Accept-Encoding": "gzip, deflate, br",   # accept compressed responses
    "Referer": "https://www.ebay.com/",       # look like in-site navigation
    "Connection": "keep-alive",
}

# rotate the User-Agent across a list for larger jobs
import random
USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Version/17.0 Safari/605.1.15",
]
HEADERS["User-Agent"] = random.choice(USER_AGENTS)

















Usa proxies residenciales rotativos

Las cabeceras no bastan a volumen: eBay puntúa la reputación de IP, y los proxies de centro de datos se marcan mucho más rápido que los residenciales. Usa IPs residenciales rotativas para scraping de búsqueda a gran volumen (una IP nueva por petición) y sesiones sticky para flujos de listado o subasta donde importa una sesión estable. Los proxies residenciales de DataImpulse cuestan 1 $/GB, son de pago por uso y se conectan directamente en la llamada a requests.



# Route requests through DataImpulse rotating residential proxies
proxies = {
    "http":  "http://LOGIN:[email protected]:823",
    "https": "http://LOGIN:[email protected]:823",
}
html = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)






Segmentación geográfica: scrapear datos de eBay por región

Los listados de eBay difieren por país: eBay.com, eBay.co.uk y eBay.de devuelven precios, disponibilidad y envíos distintos para el mismo producto. Para ver los datos que ve un comprador de un país concreto, enruta por un proxy ubicado allí. DataImpulse tiene IPs residenciales en 195 países, incluida la segmentación a nivel de estado en EE. UU., para que tu scraping de datos de eBay refleje el mercado correcto. El mismo enfoque impulsa la comparación de precios entre regiones.


Paso 8 — Escala tu scraper de eBay

Tres niveles convierten el script básico en una forma lista para producción de hacer scraping de eBay con Python a escala, por orden de complejidad creciente: un retardo aleatorio entre peticiones para no superar los límites de tasa, lógica de reintento con backoff exponencial para absorber respuestas 429/403, y recopilación asíncrona con httpx + asyncio para volumen en paralelo.



import time, random, asyncio, httpx

# 1) Basic pacing - a random delay between requests
def polite_get(url, **kw):
    time.sleep(random.uniform(1, 3))
    return requests.get(url, headers=HEADERS, timeout=30, **kw)

# 2) Retry with exponential backoff on 429/403
def get_with_retry(url, retries=4, **kw):
    for attempt in range(retries):
        r = requests.get(url, headers=HEADERS, timeout=30, **kw)
        if r.status_code not in (429, 403):
            return r
        time.sleep(2 ** attempt + random.random())   # 1s, 2s, 4s, 8s ...
    return r

# 3) Async collection for high volume (httpx >=0.28 uses proxy=, not proxies=)
async def scrape_many(urls, proxy=None):
    async with httpx.AsyncClient(headers=HEADERS, proxy=proxy, timeout=30) as client:
        tasks = [client.get(u) for u in urls]
        return await asyncio.gather(*tasks)





















Preguntas frecuentes

¿Es legal hacer scraping de eBay?

Depende. Scrapear datos públicamente disponibles puede ser lícito en algunos contextos, pero el Acuerdo de Usuario de eBay restringe el acceso automatizado —bots, scrapers, herramientas de minería de datos— sin permiso previo, así que el scraping puede incumplir sus términos y conllevar riesgo legal. Usar proxies en sí es legal. Respeta los límites de tasa, no scrapees tras un login, evita datos personales y consulta asesoría legal para uso comercial. Esto es información general, no asesoramiento legal: consulta nuestra guía sobre si el web scraping es legal.

¿Necesito proxies para scrapear eBay?

Para unas pocas peticiones, no. Para cualquier volumen real, sí: eBay puntúa la reputación de IP y limita la tasa con dureza, así que una sola IP se bloquea rápido. Los proxies residenciales rotativos reparten las peticiones entre muchas IPs y te permiten segmentar países concretos para precios correctos por región.

¿Cómo scrapeo las variantes de producto de eBay?

Las variantes como talla y color no están en el HTML visible: eBay las guarda en un objeto JSON inline (MSKU) dentro de una etiqueta script. Localiza ese objeto con re.search() y parséalo con json.loads(), como en el Paso 4.

¿Por qué mi scraper de eBay devuelve resultados vacíos?

Casi siempre son las cabeceras. Sin un User-Agent de navegador real (e idealmente Accept-Language y Referer), eBay devuelve con frecuencia un 403 o un 200 con cuerpo vacío. Añade el conjunto completo de cabeceras del Paso 7; si persiste, probablemente estés limitado por tasa o marcado por IP, así que baja el ritmo y enruta por un proxy residencial.

¿Cómo manejo la paginación de eBay en Python?

Incrementa el parámetro de URL _pgn (&_pgn=2, 3…) en un bucle y detente cuando el contenedor de resultados (li.s-item) esté vacío o alcances un tope de páginas, como en la función de paginación del Paso 3.


Conclusión

Así se hace scraping de eBay con Python de principio a fin: scrapers de resultados y de listados, paginación, extracción de variantes ocultas, reseñas, exportación JSON/CSV y las cabeceras más los proxies residenciales rotativos que mantienen en marcha tu setup de eBay scraper en Python. Empieza por lo pequeño con el scraper de búsqueda y añade luego paginación, cabeceras anti-bot y escalado a medida que crece tu volumen. Para las IPs correctas, consulta mejores proxies para scraping de eBay.

Última actualización: 24 de junio de 2026.



Share article: