API eBay vs scraping HTML : le compromis fondamental
Si vous cherchez à scrapeer eBay à grande échelle, la première décision est architecturale : utiliser les API officielles d'eBay ou extraire directement le HTML. Les deux approches ont leur place, mais le choix dépend de vos quotas, de la fraîcheur des données et de la couverture que vous visez.
eBay propose trois API principales pour les développeurs :
- Finding API — recherche et filtres avancés sur les annonces actives. Limite : 5 000 appels/jour en sandbox, jusqu'à ~1,5 million d'appels/jour en production selon le plan.
- Browse API — accès aux détails d'une annonce individuelle (item specifics, prix, livraison). Même plafond global que le Finding API dans le quota partagé.
- Trading API — opérations de compte (vente, gestion d'annonces). Moins pertinent pour le scraping passif.
Le problème ? Le quota de production n'est pas garanti — il dépend de votre API App ID et de l'historique d'utilisation. Et surtout, les API ne retournent pas tout ce que le HTML affiche : les données de feedback vendeur détaillées, les filtres de recherche visuels, les variantes de prix dynamiques sont souvent absentes ou tronquées.
Les API eBay sont idéales pour des requêtes ciblées et légères. Dès que vous devez monitorer des milliers de catégories en parallèle ou capturer des données que l'API omet, le scraping HTML devient la seule option viable.
Quand scraper plutôt qu'appeler l'API
Voici une comparaison directe pour vous aider à trancher :
| Critère | API eBay (Finding/Browse) | Scraping HTML |
|---|---|---|
| Quota quotidien | Variable, souvent plafonné à ~1,5M/jour | Limité par votre infrastructure proxy |
| Couverture des données | Champs structurés, pas de DOM | Tout ce qui est rendu dans la page |
| Données d'enchères en temps réel | Partielles via GetItemStatus | Complètes (compteur de bids, timer) |
| Feedback vendeur détaillé | Score global uniquement | Graphe complet, catégories, commentaires |
| Geo-spécificité (eBay.de, eBay.co.uk) | Paramètre siteid disponible | IP locale + domaine régional |
| Risque de blocage | Quota exceeded → HTTP 429 | IP bans, CAPTCHAs, JS challenges |
| Coût d'infrastructure | Gratuit jusqu'au quota | Coût des proxies résidentiels |
En pratique, les équipes de market research et de renseignement concurrentiel combinent les deux : l'API pour les lookups ponctuels, le scraping pour les crawls massifs et les données que l'API ne fournit pas.
Structure HTML d'eBay : les sélecteurs qui comptent
eBay utilise un rendu côté serveur pour les pages de résultats — bonne nouvelle, pas besoin de headless browser pour la plupart des cas. Voici les structures clés à cibler.
Page de résultats de recherche
L'URL canonique d'une recherche :
https://www.ebay.com/sch/i.html?_nkw=keyword+here&_sop=12&_pgn=1Paramètres utiles :
_nkw— terme de recherche_sop— tri (12 = fin prochaine, 1 = meilleur match, 15 = prix + livraison croissant)_pgn— numéro de page_ipg— résultats par page (240 max)LH_Sold=1 — inclure les objets vendusLH_Auction=1 — enchères uniquementLH_BIN=1 — Buy-It-Now uniquement
Chaque annonce est encapsulée dans un <li class="s-item">. Les sous-sélecteurs critiques :
- Titre :
.s-item__titleouh3.s-item__title > a - Prix :
.s-item__price - URL de l'annonce :
.s-item__link(attributhref) - Livraison :
.s-item__shipping - Localisation :
.s-item__location - Badge BIN :
.s-item__formatBannercontient « Buy It Now» - Compteur d'enchères :
.s-item__bidCount - Timer :
.s-item__time-left - Image :
.s-item__image-wrapper > img(attributsrc)
Page de détail d'une annonce
URL typique : https://www.ebay.com/itm/1234567890
Sélecteurs importants :
- Item specifics :
#viTabs_0_is— tableau de caractéristiques - Prix actuel :
[itemprop='price'] - Historique des enchères :
#BidHistory(visible si connecté) - Info vendeur :
.x-sellercardou#seller-info - Description : via iframe
#desc_ifr— attention, c'est du contenu cross-origin
Page de profil vendeur
URL : https://www.ebay.com/usr/SELLER_NAME
- Score de feedback :
.x-feedback-score - Pourcentage positif :
.x-feedback-pct - Catégories actives :
.x-seller-items__category - Annones actives :
.x-seller-items__count
Technologie anti-bot d'eBay
eBay utilise plusieurs couches de protection :
- Rate limiting par IP — environ 100-200 requêtes/minute depuis une même IP avant déclenchement d'un CAPTCHA ou d'un HTTP 429.
- Fingerprinting navigateur — headers TLS (JA3), User-Agent, Accept-Language doivent être cohérents.
- JavaScript challenges — principalement sur les pages de détail et les pages vendeur. Les résultats de recherche restent principalement SSR.
- AKAMAI Bot Manager — détecte les patterns de crawling par signature comportementale.
C'est pourquoi les proxies datacenter sont quasi inutilisables sur eBay à grande échelle : les plages d'IP datacenter sont pré-identifiées par Akamai. Les proxies résidentiels sont essentiels.
Choisir le bon proxy pour eBay
Proxies résidentiels : le choix par défaut
eBay bloque les IPs datacenter de manière agressive. Pour tout scrape dépassant ~50 pages, les proxies résidentiels sont obligatoires. Ils simulent du trafic d'utilisateurs réels et passent les filtres d'Akamai.
Geo-ciblage pour les marketplaces régionaux
eBay adapte ses résultats en fonction de votre localisation IP. Pour eBay.de, eBay.co.uk ou eBay.fr, vous devez utiliser des IPs locales :
- eBay.de → proxy en Allemagne (
country-DE) - eBay.co.uk → proxy au Royaume-Uni (
country-GB) - eBay.fr → proxy en France (
country-FR) - eBay.com.au → proxy en Australie (
country-AU)
Avec ProxyHat, le geo-ciblage se configure directement dans le nom d'utilisateur :
# Proxy résidentiel US (par défaut)
http://user-country-US:pass@gate.proxyhat.com:8080
# Proxy résidentiel pour eBay.de
http://user-country-DE:pass@gate.proxyhat.com:8080
# Proxy résidentiel pour eBay.co.uk
http://user-country-GB:pass@gate.proxyhat.com:8080
# Proxy avec ville ciblée (Berlin)
http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080Sessions sticky vs rotation par requête
- Rotation par requête — idéale pour les pages de résultats de recherche (chaque page = nouvelle IP). Limite le risque de blocage par IP.
- Sessions sticky — nécessaires pour les pages de détail où eBay vérifie la cohérence de session (cookies, panier). Utilisez le flag
session-:
# Session sticky de 10-30 min
http://user-session-abc123:pass@gate.proxyhat.com:8080Exemple Python : scraper les résultats de recherche eBay
Voici un eBay listing scraper complet en Python, utilisant des proxies résidentiels ProxyHat et BeautifulSoup :
import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass, asdict
import json, time, random
@dataclass
class EbayListing:
title: str
price: str
shipping: str
url: str
item_id: str
is_bin: bool
bid_count: str | None
time_left: str | None
location: str | None
PROXY = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY, "https": PROXY}
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",
"Accept": "text/html,application/xhtml+xml",
}
def build_search_url(keyword: str, page: int = 1, sort: str = "12") -> str:
return (
f"https://www.ebay.com/sch/i.html?"
f"_nkw={requests.utils.quote(keyword)}"
f"&_sop={sort}&_pgn={page}&_ipg=240"
)
def parse_listing(li) -> EbayListing | None:
try:
title_el = li.select_one(".s-item__title")
if not title_el or "Shop on eBay" in title_el.get_text():
return None
link = li.select_one(".s-item__link")
url = link["href"].split("?")[0] if link else ""
item_id = url.rstrip("/").split("/")[-1] if url else ""
price = li.select_one(".s-item__price").get_text(strip=True) if li.select_one(".s-item__price") else ""
shipping = li.select_one(".s-item__shipping").get_text(strip=True) if li.select_one(".s-item__shipping") else ""
is_bin = bool(li.select_one(".s-item__formatBanner"))
bid_el = li.select_one(".s-item__bidCount")
bid_count = bid_el.get_text(strip=True) if bid_el else None
time_el = li.select_one(".s-item__time-left")
time_left = time_el.get_text(strip=True) if time_el else None
loc_el = li.select_one(".s-item__location")
location = loc_el.get_text(strip=True) if loc_el else None
return EbayListing(
title=title_el.get_text(strip=True),
price=price, shipping=shipping, url=url,
item_id=item_id, is_bin=is_bin,
bid_count=bid_count, time_left=time_left,
location=location,
)
except Exception as e:
print(f" [!] Parse error: {e}")
return None
def scrape_ebay_search(keyword: str, max_pages: int = 5) -> list[EbayListing]:
results = []
for page in range(1, max_pages + 1):
url = build_search_url(keyword, page)
print(f"[*] Page {page}: {url}")
resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=30)
if resp.status_code != 200:
print(f" [!] HTTP {resp.status_code} — arrêt")
break
soup = BeautifulSoup(resp.text, "html.parser")
items = soup.select("li.s-item")
print(f" → {len(items)} éléments trouvés")
for li in items:
listing = parse_listing(li)
if listing:
results.append(listing)
# Délai aléatoire pour simuler un humain
time.sleep(random.uniform(2, 5))
return results
if __name__ == "__main__":
listings = scrape_ebay_search("vintage rolex", max_pages=3)
for l in listings[:10]:
print(json.dumps(asdict(l), indent=2))
print(f"\nTotal: {len(listings)} annonces extraites")Sortie exemple (tronquée) :
{
"title": "Vintage Rolex Submariner 1680",
"price": "$4,850.00",
"shipping": "+$25.00 shipping",
"url": "https://www.ebay.com/itm/3345678901",
"item_id": "3345678901",
"is_bin": true,
"bid_count": null,
"time_left": null,
"location": "New York,United States"
}Gérer les enchères : bids, timers et drapeaux BIN
Les enchères eBay nécessitent une attention particulière car les données changent en temps réel. Voici les stratégies selon votre objectif :
Monitoring en temps réel des enchères
Les pages de résultats affichent un compteur de bids (.s-item__bidCount) et un timer (.s-item__time-left) pour les annonces aux enchères. Pour un suivi rapproché :
- Rotation rapide — interrogez la même recherche toutes les 60-90 secondes avec une nouvelle IP résidentielle à chaque cycle.
- Sticky session par annonce — pour les pages de détail, gardez la même IP pendant 10-15 minutes pour maintenir les cookies de session.
- Détection de la fin d'enchère — quand
.s-item__time-leftpasse en dessous de 5 minutes, augmentez la fréquence de polling à 15-30 secondes.
Extraire les données d'enchères depuis la page de détail
def parse_auction_detail(soup) -> dict:
"""Extrait les données d'enchère depuis une page de détail eBay."""
result = {
"current_price": None,
"bid_count": None,
"time_left_seconds": None,
"is_bin": False,
"bin_price": None,
"bid_history_url": None,
}
# Prix actuel
price_el = soup.select_one("[itemprop='price']")
if price_el:
result["current_price"] = price_el.get("content") or price_el.get_text(strip=True)
# Compteur de bids
bid_count_el = soup.select_one("#vi-remaining-bid-count")
if bid_count_el:
result["bid_count"] = bid_count_el.get_text(strip=True)
# Timer (valeur data-attribute si disponible)
timer_el = soup.select_one("#vi-time-left")
if timer_el and timer_el.get("data-timestamp"):
result["time_left_seconds"] = int(timer_el["data-timestamp"]) // 1000
# Drapeau Buy-It-Now
bin_el = soup.select_one("#binBtn_btn")
if bin_el:
result["is_bin"] = True
bin_price_el = soup.select_one("#prcIsum-bin")
if bin_price_el:
result["bin_price"] = bin_price_el.get_text(strip=True)
# Lien historique des enchères
hist_el = soup.select_one("a[href*='bidhistory']")
if hist_el:
result["bid_history_url"] = hist_el["href"]
return resultPattern de sniping : ne pas abuser
Le scraping d'enchères en temps réel peut être utilisé pour du sniping (enchère à la dernière seconde). Attention : eBay détecte les patterns de sniping automatisé et peut suspendre les comptes. Utilisez ces données pour de l'analyse de marché, pas pour de la manipulation d'enchères.
Analytique vendeur : feedback, catégories et cross-listing
Le profil vendeur est une mine d'or pour le renseignement concurrentiel. Voici comment extraire et structurer ces données.
Scraping du profil vendeur
URL : https://www.ebay.com/usr/{seller_name}
Cette page contient :
- Score de feedback — nombre total de transactions évaluées
- Pourcentage positif — fiabilité perçue
- Catégories actives — dans quelles catégories le vendeur liste ses produits
- Nombre d'annonces actives — volume de stock actuel
- Date d'inscription — ancienneté du compte
def scrape_seller_profile(seller_name: str) -> dict:
url = f"https://www.ebay.com/usr/{seller_name}"
resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
profile = {
"seller": seller_name,
"feedback_score": None,
"positive_pct": None,
"active_listings_count": None,
"categories": [],
"member_since": None,
}
# Score de feedback
score_el = soup.select_one(".x-feedback-score")
if score_el:
profile["feedback_score"] = score_el.get_text(strip=True)
# Pourcentage positif
pct_el = soup.select_one(".x-feedback-pct")
if pct_el:
profile["positive_pct"] = pct_el.get_text(strip=True)
# Catégories actives
cat_els = soup.select(".x-seller-items__category")
profile["categories"] = [c.get_text(strip=True) for c in cat_els]
# Nombre d'annonces actives
count_el = soup.select_one(".x-seller-items__count")
if count_el:
profile["active_listings_count"] = count_el.get_text(strip=True)
# Date d'inscription
since_el = soup.select_one(".x-member-since")
if since_el:
profile["member_since"] = since_el.get_text(strip=True)
return profileDétection de cross-listing
Les revendeurs listent souvent les mêmes produits sur plusieurs marchés eBay. Pour détecter les patterns de cross-listing :
- Scrapez les résultats de recherche pour un mot-clé sur
ebay.com,ebay.deetebay.co.uksimultanément en utilisant des proxies geo-ciblés. - Comparez les titres normalisés (lowercase, suppression des espaces) et les images (hash perceptuel).
- Croisez les noms de vendeurs — un même vendeur sur plusieurs domaines eBay est un signal fort de cross-listing.
Exemple de requêtes parallèles avec geo-ciblage :
import concurrent.futures
DOMAINS = {
"US": "https://www.ebay.com",
"DE": "https://www.ebay.de",
"GB": "https://www.ebay.co.uk",
}
def get_geo_proxy(country: str) -> dict:
proxy = f"http://user-country-{country}:PASSWORD@gate.proxyhat.com:8080"
return {"http": proxy, "https": proxy}
def scrape_regional(keyword: str, country: str) -> list:
base = DOMAINS[country]
url = f"{base}/sch/i.html?_nkw={keyword}&_ipg=240"
proxies = get_geo_proxy(country)
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
return [parse_listing(li) for li in soup.select("li.s-item") if parse_listing(li)]
def cross_market_scan(keyword: str) -> dict:
results = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
futures = {
pool.submit(scrape_regional, keyword, c): c
for c in DOMAINS
}
for f in concurrent.futures.as_completed(futures):
country = futures[f]
results[country] = f.result()
return resultsBonnes pratiques et considérations éthiques
- Respectez robots.txt — eBay autorise le crawling partiel mais restreint certains chemins. Vérifiez
https://www.ebay.com/robots.txtrégulièrement. - Limitez votre taux de requêtes — 1-2 requêtes/seconde par IP est un maximum raisonnable. Les rafales sont immédiatement détectées.
- Conformité GDPR/CCPA — les données vendeur (nom, localisation, historique) sont des données personnelles en Europe. Stockez-les de manière conforme.
- Données de prix — les prix extraits ne doivent pas être utilisés pour de la manipulation de marché ou du contournement de la concurrence.
- Termes de service — le scraping massif viole généralement les ToS d'eBay. Utilisez les API officielles autant que possible et limitez le scraping aux cas où l'API ne suffit pas.
Points clés à retenir
- Les API eBay (Finding/Browse) couvrent les besoins basiques mais imposent des quotas et omettent des données critiques (feedback détaillé, enchères temps réel, cross-listing).
- Le sélecteur
li.s-itemest la clé de voûte du parsing des résultats de recherche eBay — tous les champs importants en dérivent.- Les proxies datacenter sont inutilisables sur eBay à cause d'Akamai — les proxies résidentiels sont obligatoires pour tout scrape au-delà de ~50 pages.
- Le geo-ciblage est essentiel pour les marketplaces régionaux (eBay.de, eBay.co.uk, eBay.fr) — configurez-le via le nom d'utilisateur ProxyHat.
- Les enchères nécessitent un polling fréquent avec rotation d'IP et des sessions sticky pour les pages de détail.
- L'analytique vendeur (feedback, catégories, cross-listing) est la donnée la plus stratégique — et la moins accessible via l'API.
Prêt à scraper eBay à grande échelle ? Consultez notre page de tarification pour les plans de proxies résidentiels, ou explorez nos localisations disponibles pour le geo-ciblage. Pour d'autres guides techniques, lisez notre article sur le scraping web avec proxies résidentiels.






