Il Dilemma API vs HTML: Perché lo Scraping di eBay è Ancora Necessario
eBay offre due famiglie di API pubbliche — Finding API e Browse API — ma la realtà operativa è che le quote e i limiti di accesso le rendono insufficienti per molti casi d'uso di market intelligence e reselling. Se il tuo team deve monitorare decine di migliaia di listing al giorno, tracciare aste in tempo reale o analizzare pattern cross-listing dei seller, prima o poi dovrai fare scraping HTML.
Il punto non è "API o scraping" — è quando passare dall'uno all'altro. Questa guida ti dà i selettori concreti, le soglie di rate-limit note, e un stack proxy che funziona davvero contro le difese anti-bot di eBay.
eBay Finding API e Browse API: Quote, Limiti e Quando Abbandonare
Finding API
La Finding API è l'endpoint REST per ricerche strutturate. I limiti chiave:
- Quota giornaliera: 5.000 chiamate/giorno per App ID (production), raddoppiabile con approvazione.
- Rate limit: ~10 richieste/secondo per applicazione.
- Dati restituiti: titolo, prezzo, itemId, localizzazione, shipping — ma nessun HTML della pagina listing, niente contatori di bid in tempo reale, niente immagini della gallery completa.
- Copertura: non tutti i marketplace locali espongono gli stessi campi. eBay.de ed eBay.co.uk possono avere gap nei dati rispetto a eBay.com.
Browse API
La Browse API offre dettagli più ricchi (description, immagini, compatibilità), ma:
- Richiede un token OAuth per utente — non basta l'App ID.
- La quota è ancor più restrittiva: spesso 3.000 chiamate/giorno.
- Alcuni campi (condition description, seller feedback dettagliato) sono assenti o troncati.
Quando Passare allo Scraping
Lo scraping diventa la scelta pragmatica quando:
- Superi le 5.000 chiamate/giorno e il processo di aumento quota di eBay non è abbastanza veloce.
- Ti servono dati in tempo reale sulle aste (bid count, time left al secondo).
- Devi estrarre feedback score, categoria del seller, e pattern di cross-listing — dati non disponibili via API.
- Vuoi monitorare varianti regionali di listing (prezzi diversi su eBay.de vs eBay.com per lo stesso item).
Regola pratica: usa l'API per ricerche mirate ("trovami iPhone 15 Pro 256GB su eBay.it") e lo scraping per monitoraggio bulk, auction tracking e seller intelligence.
Struttura HTML di eBay: Selettori CSS e XPath che Contano
eBay usa un DOM relativamente stabile per le pagine di ricerca e listing. Ecco i selettori che funzionano al momento della scrittura.
Pagina dei Risultati di Ricerca — La Griglia .s-item
L'URL canonico per la ricerca: https://www.ebay.com/sch/i.html?_nkw=KEYWORD&_pgn=PAGE
Ogni risultato è un <li> con classe .s-item. All'interno:
| Dato | Selettore CSS | Note |
|---|---|---|
| Titolo | .s-item__title | Testo puro, contiene span per "NEW LISTING" |
| Prezzo | .s-item__price | Può contenere range (es. "$50.00 to $120.00") |
| Spedizione | .s-item__shipping | "Free shipping", "$5.99 shipping" |
| URL listing | .s-item__link → href | Contiene tracking params — puliscili |
| Condizione | .s-item__item-condition | "New", "Used", "Refurbished" |
| Stato asta | .s-item__bid-count | Presente solo per aste attive |
| Buy It Now | .s-item__buy-it-now | Flag visivo, non sempre presente |
| Immagine | .s-item__image-img → src | URL immagine thumbnail |
| Seller info | .s-item__seller-info | Nome + feedback % |
Pagina Dettaglio Listing
URL: https://www.ebay.com/itm/ITEM_ID
- Prezzo corrente:
#prcIsumo[data-testid='x-price-section'] - Tempo rimanente asta:
#vi-cdown_time— formato "XXd XXh XXm XXs" - Numero bid:
#bidBtn_btncontiene il testo "XX bids" - Buy It Now flag: presenza di
#binBtn_btn - Descrizione: caricata in iframe
#desc_ifr— richiede richiesta separata all'URL nell'src - Feedback seller:
.mbg-n(nome),.mbg-l(link feedback),.info__dot(score)
Pagina Profilo Seller
URL: https://www.ebay.com/usr/SELLER_NAME
- Feedback score:
.str-profile__feedback-score - Percentuale positiva:
.str-profile__feedback-percentage - Categorie attive:
.str-categorical-labels__category - Data registrazione:
.str-profile__registration-date
Proxy per lo Scraping di eBay: Residenziali, Geo-Targeting e Rotazione
eBay è uno dei siti più aggressivi nel bloccare IP datacenter. Il loro sistema anti-bot (basato su Thumbprint/Shape e Arkose Labs per le pagine di login) rileva IP DC con alta precisione. Un IP datacenter pulito può resistere 5-15 richieste prima del blocco; un IP residenziale pulito può gestire 200-500 richieste prima che si attivi un CAPTCHA.
Residenziali vs Datacenter vs Mobile
| Tipo Proxy | Durata Media prima del Blocco | Costo | Caso d'Uso |
|---|---|---|---|
| Datacenter | 5-15 richieste | Basso | Testing, API calls, poche pagine |
| Residenziale (rotante) | 200-500 richieste per IP | Medio | Scraping bulk, SERP monitoring |
| Residenziale (sticky) | 10-30 min per sessione | Medio | Auction tracking, multi-page scraping |
| Mobile | 500+ richieste per IP | Alto | Scraping intensivo, alta affidabilità |
Geo-Targeting per Marketplace Regionali
eBay localizza prezzi e listing disponibili in base alla posizione. Per accedere a eBay.de o eBay.co.uk con listing completi, devi uscire da un IP del paese corrispondente:
# German IP per eBay.de
http://user-country-DE:PASSWORD@gate.proxyhat.com:8080
# UK IP per eBay.co.uk
http://user-country-GB:PASSWORD@gate.proxyhat.com:8080
# Italia per eBay.it
http://user-country-IT:PASSWORD@gate.proxyhat.com:8080
Rotazione per-Richiesta vs Sticky Session
- Per-richiesta (default): ogni HTTP request ottiene un IP diverso. Ideale per scraping bulk di search results dove ogni pagina è indipendente.
- Sticky session: mantieni lo stesso IP per 10-30 minuti. Necessaria quando devi navigare tra pagine di ricerca e dettaglio listing senza che eBay rilevi un cambio IP a metà sessione.
# Sticky session — mantieni lo stesso IP per la durata della sessione
http://user-session-mysearch01-country-US:PASSWORD@gate.proxyhat.com:8080
Per approfondire le strategie di rotazione, consulta la nostra guida alla rotazione IP con proxy residenziali.
Esempio Python: Scrape dei Risultati di Ricerca eBay
Questo script fa una ricerca su eBay, ruota l'IP per ogni pagina tramite ProxyHat, e estrae i dati strutturati da ogni .s-item.
import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass, asdict
import json, time, random
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",
}
@dataclass
class EbayListing:
title: str
price: str
shipping: str
condition: str
url: str
bid_count: str = ""
buy_it_now: bool = False
seller: str = ""
def scrape_search(keyword: str, max_pages: int = 3) -> list[EbayListing]:
results = []
for page in range(1, max_pages + 1):
# Rotazione IP: usa sessione diversa per ogni pagina
session_id = f"ebay-p{page}-{int(time.time())}"
proxy = f"http://user-session-{session_id}-country-US:PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy, "https": proxy}
url = f"https://www.ebay.com/sch/i.html?_nkw={keyword}&_pgn={page}"
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
if resp.status_code != 200:
print(f"Pagina {page}: HTTP {resp.status_code} — possibile blocco")
break
soup = BeautifulSoup(resp.text, "html.parser")
items = soup.select(".s-item")
print(f"Pagina {page}: trovati {len(items)} items")
for item in items:
title_el = item.select_one(".s-item__title")
if not title_el or "Shop on eBay" in title_el.text:
continue # salta ad fittizio
price_el = item.select_one(".s-item__price")
ship_el = item.select_one(".s-item__shipping")
cond_el = item.select_one(".s-item__item-condition")
link_el = item.select_one(".s-item__link")
bid_el = item.select_one(".s-item__bid-count")
bin_el = item.select_one(".s-item__buy-it-now")
seller_el = item.select_one(".s-item__seller-info")
listing = EbayListing(
title=title_el.text.strip(),
price=price_el.text.strip() if price_el else "N/A",
shipping=ship_el.text.strip() if ship_el else "N/A",
condition=cond_el.text.strip() if cond_el else "N/A",
url=link_el["href"].split("?")[0] if link_el else "",
bid_count=bid_el.text.strip() if bid_el else "",
buy_it_now=bool(bin_el),
seller=seller_el.text.strip() if seller_el else "",
)
results.append(listing)
# Rate limiting: 2-4s tra le pagine
time.sleep(random.uniform(2, 4))
return results
# Esecuzione
listings = scrape_search("mechanical keyboard", max_pages=3)
for l in listings[:5]:
print(json.dumps(asdict(l), indent=2))
Esempio di output (troncato):
{
"title": "Keychron Q1 HE Hall Effect Mechanical Keyboard",
"price": "$179.00",
"shipping": "Free shipping",
"condition": "New",
"url": "https://www.ebay.com/itm/1234567890",
"bid_count": "",
"buy_it_now": true,
"seller": "keychron_official (99.8%)"
}
Gestire le Aste: Time-to-End, Bid Count e Buy-It-Now
Le aste sono il dato più volatile su eBay. Il prezzo cambia ogni secondo negli ultimi minuti, e il bid count è visibile solo nella pagina di dettaglio del listing — non nella search grid (dove trovi solo .s-item__bid-count per le aste attive con bid).
Strategia per il Tracking delle Aste
- Polling con sticky session: mantieni la stessa sessione proxy per 10-15 minuti e fai polling della pagina listing ogni 30-60 secondi.
- Estrazione tempo rimanente: il selettore
#vi-cdown_timerestituisce una stringa come"2d 05h 42m"— parsa con regex. - Rilevamento sniping window: quando il tempo scende sotto i 5 minuti, aumenta la frequenza di polling a ogni 10 secondi.
import re
from datetime import datetime, timedelta
def parse_time_left(time_str: str) -> timedelta | None:
"""Converte '2d 05h 42m 12s' in timedelta."""
pattern = r"(?:(\d+)d)?\s*(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?"
match = re.match(pattern, time_str.strip())
if not match:
return None
days = int(match.group(1) or 0)
hours = int(match.group(2) or 0)
mins = int(match.group(3) or 0)
secs = int(match.group(4) or 0)
return timedelta(days=days, hours=hours, minutes=mins, seconds=secs)
def extract_auction_data(html: str) -> dict:
soup = BeautifulSoup(html, "html.parser")
# Prezzo corrente asta
price_el = soup.select_one("#prcIsum")
price = price_el.text.strip() if price_el else "N/A"
# Tempo rimanente
time_el = soup.select_one("#vi-cdown_time")
time_left = parse_time_left(time_el.text) if time_el else None
# Numero di bid
bid_btn = soup.select_one("#bidBtn_btn")
bid_count = 0
if bid_btn:
bid_match = re.search(r"(\d+) bid", bid_btn.text)
bid_count = int(bid_match.group(1)) if bid_match else 0
# Buy It Now disponibile?
has_bin = bool(soup.select_one("#binBtn_btn"))
return {
"price": price,
"time_left_seconds": int(time_left.total_seconds()) if time_left else None,
"bid_count": bid_count,
"buy_it_now_available": has_bin,
"scraped_at": datetime.utcnow().isoformat(),
}
Per il tracking continuo, combina sessioni sticky con un job scheduler (APScheduler, Celery beat) che lancia extract_auction_data a intervalli decrescenti man mano che l'asta si avvicina alla chiusura.
Seller Analytics: Feedback, Categorie e Cross-Listing
La vera intelligence per i team di reselling non è nel singolo listing — è nel profilo del seller. Ecco cosa estrarre e come usarlo.
Dati Chiave del Seller
- Feedback score e percentuale positiva: indicatori di affidabilità e volume storico.
- Categorie attive: un seller che vende in 15 categorie è probabilmente un liquidatore; uno che vende in 2 è un specialista di nicchia.
- Data registrazione: account recenti con alto volume sono segnali di attenzione.
- Pattern di cross-listing: se lo stesso seller pubblica lo stesso item su eBay.com ed eBay.de con prezzi diversi, c'è un'opportunità di arbitrage.
Cross-Listing Detection
Per rilevare cross-listing, confronta i listing di uno stesso seller su marketplace diversi:
- Scrape i listing del seller su eBay.com (IP US).
- Scrape gli stessi listing su eBay.de (IP DE) usando geo-targeting.
- Matcha per titolo normalizzato (lowercase, rimuovi punteggiatura) + condizione.
- Confronta i prezzi — differenze > 10% dopo conversione valutaria indicano arbitrage potenziale.
def scrape_seller_profile(seller_name: str, country: str = "US") -> dict:
"""Estrae dati profilo seller con geo-targeting."""
proxy = f"http://user-country-{country}:PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy, "https": proxy}
# Seleziona il dominio corretto
domain_map = {"US": "ebay.com", "DE": "ebay.de", "GB": "ebay.co.uk", "IT": "ebay.it"}
domain = domain_map.get(country, "ebay.com")
url = f"https://www.{domain}/usr/{seller_name}"
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
feedback_score = soup.select_one(".str-profile__feedback-score")
feedback_pct = soup.select_one(".str-profile__feedback-percentage")
categories = [el.text.strip() for el in soup.select(".str-categorical-labels__category")]
reg_date = soup.select_one(".str-profile__registration-date")
return {
"seller": seller_name,
"marketplace": domain,
"feedback_score": feedback_score.text.strip() if feedback_score else "N/A",
"positive_pct": feedback_pct.text.strip() if feedback_pct else "N/A",
"categories": categories,
"registration_date": reg_date.text.strip() if reg_date else "N/A",
}
Anti-Bot di eBay: Cosa Ti Colpisce e Come Mitigare
eBay usa un stack anti-bot stratificato:
- Fingerprinting browser (Shape/Thumbprint): rileva discrepanze tra User-Agent, TLS fingerprint e comportamento. Mitigazione: usa header consistenti e, se possibile, browser realistici con
playwrightoundetected-chromedriver. - Rate limiting per IP: ~200 richieste/10 minuti per IP residenziale, ~15 per IP datacenter. Mitigazione: rotazione IP per-request e rate limiting lato client (2-4s tra richieste).
- CAPTCHA (Arkose Labs / FunCaptcha): triggerato dopo ripetuti pattern sospetti. Mitigazione: riduci la velocità, ruota IP, usa sessioni sticky per non cambiare IP a metà flusso.
- JavaScript challenge: alcune pagine richiedono esecuzione JS per caricare contenuti. Mitigazione: per la search grid, il contenuto è nel HTML iniziale — non serve JS. Per le pagine seller, potrebbe essere necessario un browser headless.
Per una panoramica più ampia sulle tecniche di scraping resiliente, consulta il nostro caso d'uso sul web scraping.
Considerazioni Etiche e Legali
Lo scraping di eBay solleva questioni concrete:
- robots.txt: eBay permette l'accesso bot a
/sch/(search) ma blocca/itm/detail pages per molti user-agent. Verifica semprehttps://www.ebay.com/robots.txtprima di procedere. - Terms of Service: i ToS di eBay proibiscono lo scraping automatizzato senza autorizzazione. Questo è un rischio legale che il tuo team deve valutare.
- GDPR/CCPA: i dati del seller (nome, feedback) sono dati personali. Se li archivi e li elabori nell'UE, applica i principi GDPR.
- Rate limiting responsabile: anche se puoi fare 500 richieste/minuto con proxy rotanti, non significa che dovresti. Rispetta il server — mantieni 2-4 secondi tra le richieste.
Lo scraping etico non è solo compliance — è anche longevità. I proxy che rispettano i rate limit durano mesi; quelli che bombardano un sito vengono bloccati in giorni e rovinano la reputazione dell'IP pool.
Punti Chiave
- API prima, scraping dopo: usa Finding/Browse API per ricerche mirate (<5K call/giorno). Passa allo scraping per bulk, auction tracking e seller analytics.
- Selettori stabili:
.s-itemper la griglia di ricerca,#prcIsume#vi-cdown_timeper i dettagli asta,.str-profile__feedback-scoreper i seller. - Proxy residenziali obbligatori: eBay blocca IP datacenter dopo 5-15 richieste. I residenziali ti danno 200-500 richieste per IP.
- Geo-targeting per marketplace regionali: usa IP del paese corretto per eBay.de, eBay.co.uk, eBay.it — altrimenti vedrai listing e prezzi diversi.
- Sticky session per aste: mantieni lo stesso IP durante il tracking di un'asta per evitare rilevamento anti-bot.
- Rate limiting lato client: 2-4 secondi tra richieste, anche con proxy rotanti. È la difesa più efficace contro i CAPTCHA.
Se il tuo team ha bisogno di un pool proxy residenziale affidabile per lo scraping di eBay, dai un'occhiata alle soluzioni ProxyHat — con geo-targeting per paese e città, sticky session, e rotazione per-request integrata. Puoi anche esplorare le nostre locazioni disponibili per coprire tutti i marketplace regionali di eBay.






