Guida Pratica allo Scraping di eBay con Proxy: API, Selettori e Strategie di Rotazione

Scopri come scrape eBay efficacemente: quando usare le API ufficiali vs scraping HTML, selettori CSS/XPath per i risultati, proxy residenziali per evitare blocchi, e un esempio Python completo per estrarre listing e aste.

Guida Pratica allo Scraping di eBay con Proxy: API, Selettori e Strategie di Rotazione

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:

DatoSelettore CSSNote
Titolo.s-item__titleTesto puro, contiene span per "NEW LISTING"
Prezzo.s-item__pricePuò contenere range (es. "$50.00 to $120.00")
Spedizione.s-item__shipping"Free shipping", "$5.99 shipping"
URL listing.s-item__linkhrefContiene tracking params — puliscili
Condizione.s-item__item-condition"New", "Used", "Refurbished"
Stato asta.s-item__bid-countPresente solo per aste attive
Buy It Now.s-item__buy-it-nowFlag visivo, non sempre presente
Immagine.s-item__image-imgsrcURL immagine thumbnail
Seller info.s-item__seller-infoNome + feedback %

Pagina Dettaglio Listing

URL: https://www.ebay.com/itm/ITEM_ID

  • Prezzo corrente: #prcIsum o [data-testid='x-price-section']
  • Tempo rimanente asta: #vi-cdown_time — formato "XXd XXh XXm XXs"
  • Numero bid: #bidBtn_btn contiene 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 ProxyDurata Media prima del BloccoCostoCaso d'Uso
Datacenter5-15 richiesteBassoTesting, API calls, poche pagine
Residenziale (rotante)200-500 richieste per IPMedioScraping bulk, SERP monitoring
Residenziale (sticky)10-30 min per sessioneMedioAuction tracking, multi-page scraping
Mobile500+ richieste per IPAltoScraping 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

  1. Polling con sticky session: mantieni la stessa sessione proxy per 10-15 minuti e fai polling della pagina listing ogni 30-60 secondi.
  2. Estrazione tempo rimanente: il selettore #vi-cdown_time restituisce una stringa come "2d 05h 42m" — parsa con regex.
  3. 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:

  1. Scrape i listing del seller su eBay.com (IP US).
  2. Scrape gli stessi listing su eBay.de (IP DE) usando geo-targeting.
  3. Matcha per titolo normalizzato (lowercase, rimuovi punteggiatura) + condizione.
  4. 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 playwright o undetected-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 sempre https://www.ebay.com/robots.txt prima 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-item per la griglia di ricerca, #prcIsum e #vi-cdown_time per i dettagli asta, .str-profile__feedback-score per 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.

Pronto per iniziare?

Accedi a oltre 50M di IP residenziali in oltre 148 paesi con filtraggio AI.

Vedi i prezziProxy residenziali
← Torna al Blog