APIs de eBay vs. scraping HTML: la disyuntiva real
Si necesitas datos de eBay a escala, lo primero que debes decidir es si usas las APIs oficiales o raspas el HTML directamente. Las eBay Finding API y la Browse API parecen la opción limpia, pero tienen costes ocultos que te empujarán al scraping antes de lo que esperas.
Cuotas y límites de las APIs de eBay
- Finding API: 5 000 llamadas/día en el tier básico; sube a 10 000 si solicitas aumento. Cada llamada devuelve hasta 100 ítems (paginación de 100 páginas máximo = 10 000 ítems por consulta).
- Browse API: depende del sandbox/producción y del acuerdo comercial. En producción típica verás ~10 000 llamadas/día, con un rate limit de ~500 llamadas/min.
- Trading API: orientada a vendedores, no a lectura masiva. 15 000 llamadas/día, pero requiere tokens de usuario con permisos específicos.
- OAuth token refresh: los tokens de usuario expiran cada 18 horas; los de aplicación duran 2 años. Gestionar el refresh a escala es un punto de fallo real.
El problema no es solo la cuota: la Finding API no expone datos de subastas en tiempo real (bid count, time-to-end preciso) ni la estructura completa del feedback del vendedor. Si tu caso de uso es inteligencia de precios para reventa, esos campos son exactamente los que necesitas.
Cuándo recurrir al scraping
- Necesitas datos de subastas en vivo (bid count, tiempo restante).
- Quieres analizar perfiles de vendedor: feedback detallado, categorías de listing, patrones de cross-listing.
- Tu volumen supera los 10 000 ítems/día y el aumento de cuota tarda semanas.
- Necesitas datos de eBay regionales (eBay.de, eBay.co.uk) que la API no filtra con granularidad suficiente.
Regla práctica: usa la API para prototipos rápidos y volúmenes bajos. Migrar a scraping HTML cuando necesites profundidad de datos o escala.
Estructuras HTML objetivo en eBay
Conocer los selectores correctos te ahorra horas de depuración. Estos son los tres bloques principales que raspamos en eBay.
Grid de resultados de búsqueda: .s-item
La página de resultados (/sch/i.html?_nkw=KEYWORD) renderiza cada listing como un <li class="s-item">. Los campos clave:
| Dato | Selector CSS | Notas |
|---|---|---|
| Título | .s-item__title | Texto limpio; evita .s-item__title--tag (badges como "NEW") |
| Precio | .s-item__price | Puede incluir rango "$10.00 to $15.00" |
| Envío | .s-item__shipping | "Free shipping", "$5.99 shipping" |
| URL del listing | .s-item__link → href | Incluye tracking params; límpialos |
| Imagen | .s-item__image-img → src | Thumbnail 225×225; reemplaza s-l225 por s-l1600 para HD |
| Subasta / BIN | .s-item__bid-count, .s-item__buy-it-now | Vacío si no aplica |
| Tiempo restante | .s-item__time-left | "1h 5m left" — formato humano |
| Ubicación | .s-item__location | "from United States" |
Página de detalle del listing
La URL canónica es /itm/ITEM_ID. Selectores útiles:
- Precio actual / bid:
#prcIsumo#mm-saleDscPrc - Buy-It-Now price:
#binBtn_btn→ atributo o texto adyacente - Bid count:
#bidCounto.vi-bid-count - Tiempo restante de subasta:
#vi-cdown_timeLeft— formato "Xh Xm left" - Item specifics:
.ux-labels-values__labelsy.ux-labels-values__values— pares clave-valor - Descripción: dentro de un iframe
#desc_ifr— necesitas una segunda request alsrcdel iframe
Perfil del vendedor
URL: /usr/SELLER_NAME o /str/STORE_NAME. Campos:
- Feedback score:
.mbg-n(número entre paréntesis) - Positive feedback %:
#si-fb - Feedback detallado: en
/usr/SELLER_NAME/feedback, tabla con.fb-table - Categorías de la tienda:
.str-header__categories→ enlaces con nombres
Anti-bot de eBay: lo que te va a bloquear
eBay usa una combinación de técnicas:
- Fingerprinting de TLS/JA3: los datacenter IPs con huellas TLS de librerías de scraping (Python
requestscon defaults) se bloquean rápido. - Rate limiting por IP: ~200 requests/min desde una IP residencial antes de CAPTCHA; ~50 requests/min desde DC.
- ReCAPTCHA v3: aparece en páginas de búsqueda tras ~10 páginas seguidas desde la misma IP.
- Device fingerprint (JavaScript): si no ejecutas JS, eBay puede servir una página reducida o devolver
HTTP 202con challenge.
Por eso los proxies residenciales son prácticamente obligatorios para scraping de eBay a escala.
Selección de proxy para eBay
Residencial vs. Datacenter vs. Móvil
| Tipo | Bloqueo en eBay | Caso de uso |
|---|---|---|
| Datacenter | Alto — bloqueado tras ~50 req/min | Prototipado, APIs oficiales |
| Residencial | Bajo — ~200 req/min por IP | Scraping masivo de listings y búsqueda |
| Móvil | Muy bajo — eBay confía en IPs móviles | Alta frecuencia en subastas en vivo |
Geo-targeting para eBay regionales
Los precios y la disponibilidad cambian según el país. eBay.de muestra precios en EUR y listings exclusivos de Alemania; eBay.co.uk tiene su propio inventario. Con ProxyHat, geo-targeting se controla desde el nombre de usuario:
# eBay.de — IPs alemanas
http://user-country-DE:PASSWORD@gate.proxyhat.com:8080
# eBay.co.uk — IPs británicas
http://user-country-GB:PASSWORD@gate.proxyhat.com:8080
# eBay.com.au — IPs australianas
http://user-country-AU:PASSWORD@gate.proxyhat.com:8080
Rotación por-request vs. sesiones sticky
- Rotación por-request: cada request obtiene una IP diferente. Ideal para recorrer páginas de resultados sin disparar rate limits. Simplemente no pongas flag de sesión.
- Sticky session: mantiene la misma IP durante un tiempo (típicamente 10–30 min). Necesario cuando eBay requiere consistencia de sesión (ej. navegar página 1 → 2 → 3 sin que te resetee el carrito o filtros).
# Rotación por-request (sin flag de sesión)
http://user-country-US:PASSWORD@gate.proxyhat.com:8080
# Sticky session para paginación
http://user-country-US-session-mypagination123:PASSWORD@gate.proxyhat.com:8080
Ejemplo Python: scraper de búsqueda eBay con ProxyHat
Este script busca un keyword, recorre páginas de resultados y extrae los campos clave de cada .s-item.
import requests
from bs4 import BeautifulSoup
import csv
import time
import re
PROXY_URL = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY_URL, "https": PROXY_URL}
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
def build_search_url(keyword, page=1):
"""Construye la URL de búsqueda de eBay con paginación."""
return (
f"https://www.ebay.com/sch/i.html?"
f"_nkw={requests.utils.quote(keyword)}"
f"&_pgn={page}"
)
def parse_s_item(li):
"""Extrae datos estructurados de un elemento .s-item."""
def sel(css):
el = li.select_one(css)
return el.get_text(strip=True) if el else None
link_el = li.select_one(".s-item__link")
url = link_el["href"].split("?")[0] if link_el else None
img_el = li.select_one(".s-item__image-img")
image = img_el["src"].replace("s-l225", "s-l1600") if img_el else None
price_raw = sel(".s-item__price")
price = None
if price_raw:
m = re.search(r"[\d,]+\.?\d*", price_raw.replace(",", ""))
price = float(m.group()) if m else None
bid_count = sel(".s-item__bid-count")
is_auction = bid_count is not None
is_bin = li.select_one(".s-item__buy-it-now") is not None
time_left = sel(".s-item__time-left")
return {
"title": sel(".s-item__title"),
"price": price,
"currency": "USD", # ajustar según marketplace
"shipping": sel(".s-item__shipping"),
"url": url,
"image": image,
"is_auction": is_auction,
"bid_count": bid_count,
"is_buy_it_now": is_bin,
"time_left": time_left,
"location": sel(".s-item__location"),
}
def scrape_ebay_search(keyword, max_pages=5):
"""Raspa páginas de búsqueda de eBay y devuelve lista de dicts."""
results = []
for page in range(1, max_pages + 1):
url = build_search_url(keyword, page)
resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=30)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
items = soup.select(".s-item")
# El primer .s-item suele ser un placeholder de "Shop on eBay"
for li in items[1:]:
record = parse_s_item(li)
if record["title"]:
results.append(record)
print(f"Página {page}: {len(items) - 1} listings")
time.sleep(2) # ser respetuoso entre páginas
return results
if __name__ == "__main__":
data = scrape_ebay_search("vintage synthesizer", max_pages=3)
with open("ebay_listings.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
print(f"Guardados {len(data)} listings en ebay_listings.csv")
Manejo de subastas: bid count, tiempo restante y Buy-It-Now
Las subastas son donde el scraping HTML supera claramente a la API. La Finding API devuelve listingInfo.listingType=Auction, pero el bid count en tiempo real y el tiempo restante preciso solo están en la página de detalle.
Snippet: extraer datos de subasta desde la página de detalle
def parse_auction_detail(soup):
"""Extrae datos de subasta desde /itm/ITEM_ID."""
def sel(css):
el = soup.select_one(css)
return el.get_text(strip=True) if el else None
# Precio actual de bid
current_bid_el = soup.select_one("#prcIsum")
current_bid = None
if current_bid_el:
m = re.search(r"[\d,]+\.?\d*", current_bid_el.get_text(strip=True).replace(",", ""))
current_bid = float(m.group()) if m else None
# Buy-It-Now price (si existe)
bin_price = None
bin_btn = soup.select_one("#binBtn_btn")
if bin_btn:
bin_text = bin_btn.get_text(strip=True)
m = re.search(r"[\d,]+\.?\d*", bin_text.replace(",", ""))
bin_price = float(m.group()) if m else None
# Bid count
bid_count = sel("#bidCount") or sel(".vi-bid-count")
# Tiempo restante
time_left = sel("#vi-cdown_timeLeft")
# Item specifics
specifics = {}
labels = soup.select(".ux-labels-values__labels .ux-textsp")
values = soup.select(".ux-labels-values__values .ux-textsp")
for l, v in zip(labels, values):
key = l.get_text(strip=True).rstrip(":")
val = v.get_text(strip=True)
if key and val:
specifics[key] = val
return {
"current_bid": current_bid,
"bin_price": bin_price,
"bid_count": bid_count,
"time_left": time_left,
"is_auction": bid_count is not None,
"is_buy_it_now": bin_price is not None,
"item_specifics": specifics,
}
# Uso:
# resp = requests.get(item_url, headers=HEADERS, proxies=PROXIES, timeout=30)
# soup = BeautifulSoup(resp.text, "html.parser")
# auction_data = parse_auction_detail(soup)
Estrategia de polling para subastas
Si monitorizas subastas cerca del cierre, necesitas polling frecuente. Reglas prácticas:
- >1 hora restante: poll cada 15 minutos.
- 15 min – 1 hora: poll cada 2 minutos.
- <15 min: poll cada 30 segundos (usa IPs móviles para minimizar bloqueo).
Con ProxyHat, cambia a proxies móviles para esta fase:
# Proxy móvil para alta frecuencia
MOBILE_PROXY = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
# (ProxyHat enruta automáticamente a IPs móviles según el plan contratado)
Analítica de vendedores: feedback, categorías y cross-listing
Para equipos de inteligencia de reventa, entender al vendedor es tan importante como el precio del ítem.
Extracción del perfil del vendedor
def parse_seller_profile(soup):
"""Extrae datos del perfil de vendedor desde /usr/SELLER_NAME."""
def sel(css):
el = soup.select_one(css)
return el.get_text(strip=True) if el else None
# Feedback score (número entre paréntesis)
score_el = soup.select_one(".mbg-n")
feedback_score = int(re.search(r"\d+", score_el.get_text()).group()) if score_el else 0
# Positive feedback %
positive_pct = sel("#si-fb")
# Fecha de registro del vendedor
reg_date = sel("#memberSince")
# Categorías de la tienda
categories = []
cat_links = soup.select(".str-header__categories a")
for a in cat_links:
categories.append({
"name": a.get_text(strip=True),
"url": a["href"],
})
return {
"feedback_score": feedback_score,
"positive_feedback_pct": positive_pct,
"member_since": reg_date,
"store_categories": categories,
"category_count": len(categories),
}
Detección de cross-listing
Un vendedor que lista en múltiples categorías de eBay puede estar diversificando inventario o haciendo retail arbitrage. Para detectar esto:
- Raspa el perfil del vendedor y obtén sus categorías de tienda.
- Para cada categoría, extrae los listings y compara títulos/precios con los de otras categorías.
- Si un vendedor tiene >5 categorías activas con listings recientes, es probable que haga cross-listing o arbitraje.
Feedback detallado como señal de calidad
La página /usr/SELLER_NAME/feedback contiene una tabla con feedback por mes. Extrae:
- Positive / Neutral / Negative por mes:
.fb-table .positive,.fb-table .neutral,.fb-table .negative - Comentarios recientes:
.fb-table .fb-item-feedback-detail - Un vendedor con >2% de feedback negativo en los últimos 3 meses es una señal de alerta para equipos de reventa.
Comando curl rápido para testing
Antes de escribir código, valida que tu proxy funciona con eBay:
# Test rápido: buscar "airpods" en eBay via ProxyHat
curl -x http://user-country-US:PASSWORD@gate.proxyhat.com:8080 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
"https://www.ebay.com/sch/i.html?_nkw=airpods&_pgn=1" \
-o ebay_test.html
# Verificar que hay listings
grep -c 's-item__title' ebay_test.html
Consideraciones éticas y legales
- robots.txt: eBay permite crawling limitado de ciertas rutas, pero prohíbe scraping de
/itm/y/sch/. Respeta sus términos si operas dentro de su jurisdicción. - Rate limiting: nunca superes 1 request/segundo por IP. Usa delays de 2–5 segundos entre páginas.
- Datos personales: nombres de vendedores y ubicaciones son datos personales bajo GDPR/CCPA. Almacénalos solo si tienes base legal y los anonimizas cuando sea posible.
- ToS de eBay: el scraping masivo viola los Términos de Servicio. Si eres un negocio registrado, considera usar la API oficial con un acuerdo comercial.
Puntos clave
API vs. Scraping: Usa la Finding API para prototipos y volúmenes <10K ítems/día. Migrar a scraping HTML para datos de subastas, feedback de vendedor y escala superior.
Selectores:
.s-itempara la grid de búsqueda,#prcIsumy#bidCountpara subastas en detalle,.mbg-npara feedback del vendedor.Proxies residenciales: Obligatorios para scraping de eBay a escala. Datacenter se bloquea tras ~50 req/min; residencial aguanta ~200 req/min por IP.
Geo-targeting: Usa
user-country-DEouser-country-GBen ProxyHat para acceder a inventario regional de eBay.de o eBay.co.uk.Subastas: Aumenta la frecuencia de polling conforme se acerca el cierre. Usa proxies móviles para la fase final (<15 min).
Ética: Respeta rate limits, robots.txt y la privacidad de datos. El scraping masivo viola los ToS de eBay.
Próximos pasos
Si necesitas escalar tu scraping de eBay, configura tus proxies residenciales en ProxyHat y ejecuta el script Python de arriba como punto de partida. Para casos de uso más amplios de web scraping, consulta nuestra guía de web scraping con proxies. Y si rastreas precios de competidores en múltiples marketplaces, el caso de uso de SERP tracking te será útil.






