Scrape Etsy : API interne vs HTML — quelles options pour la recherche de niche ?
Si vous cherchez à scraper Etsy pour trouver votre prochaine niche print-on-demand, vous avez deux voies : l'API interne non documentée d'Etsy ou le parsing HTML classique. Chaque approche a des compromis très concrets.
API interne (endpoint /api/v3/ajax/member/search) — Etsy expose des endpoints JSON utilisés par son front-end React. Ils renvoient des données structurées (titres, prix, images, shop_id) sans avoir à parser le DOM. Le problème : ces endpoints sont protégés par des tokens CSRF et des headers personnalisés qui changent régulièrement, et le rate limiting y est plus agressif (~60 req/min avant blocage temporaire).
HTML scraping — Plus robuste sur le long terme. Le HTML des pages de recherche contient tout ce dont vous avez besoin via des selecteurs CSS stables. Le rate limiting est aussi présent mais légèrement plus permissif (~100-120 req/min avant déclenchement Cloudflare). C'est l'approche que nous recommandons pour la recherche de niche Etsy.
Dans ce guide, nous combinons les deux : HTML pour les résultats de recherche, API interne pour les détails de listing quand c'est disponible. Et nous utilisons des proxies résidentiels pour rester sous le radar.
Structure d'Etsy : comprendre le site avant de le scraper
Pages de recherche
L'URL de recherche d'Etsy suit ce pattern :
https://www.etsy.com/search?q=funny+mug&ref=search_bar&order=highest_reviewsParamètres utiles :
q— le terme de rechercheorder—highest_reviews,lowest_price,highest_price,newestpage— pagination (1 à ~250)location_query— filtre géographique
Chaque page affiche jusqu'à 48 listing cards. Les données clés sont dans des éléments <div data-search-results> et les cards individuelles suivent le sélecteur .v2-listing-card.
Pages de listing (détail produit)
URL : https://www.etsy.com/listing/{listing_id}/{slug}
Données disponibles : titre complet, description, prix, variantes, nombre de favoris, avis, badge ventes du shop. Le JSON embarqué dans <script type="application/ld+json"> contient la plupart des métadonnées.
Pages de shop
URL : https://www.etsy.com/shop/{shop_name}
On y trouve : nombre total de listings, badge "X sales" (ventes approximatives), avis, localisation, date de création. Le sélecteur CSS .shop-info et le JSON-LD sont vos amis.
Arbre de catégories
Etsy organise ses catégories hiérarchiquement : /c/clothing/women, /c/home-and-living/home-decor, etc. Scraper l'arbre complet permet de découvrir des niches avec peu de concurrence. L'endpoint https://www.etsy.com/api/v3/ajax/member/categories retourne parfois l'arbre en JSON, mais il est plus fiable de parser la page /categories.
Anti-bot Etsy : Cloudflare et rate limits internes
Etsy utilise Cloudflare avec le mode "Under Attack" activé dynamiquement. Voici ce que nous avons observé en production :
| Seuil | Comportement | Durée du blocage |
|---|---|---|
| ~100-120 req/min (même IP) | Challenge JavaScript Cloudflare | 5-15 min |
| ~300-500 req/min | Captcha hCaptcha | 15-60 min |
| Pattern de scraping évident | Ban IP (403 permanent) | 24h+ ou permanent |
Pourquoi les proxies résidentiels sont indispensables : les IPs datacenter sont presque immédiatement flaguées par Cloudflare. Les IPs résidentielles, en revanche, se fondent dans le trafic normal d'Etsy. C'est là qu'un proxy résidentiel de qualité fait toute la différence.
Conseils pratiques :
- Rottez l'IP toutes les 30-50 requêtes avec des sessions résidentielles
- Ajoutez un délai aléatoire de 2-5 secondes entre les requêtes
- Faites tourner les User-Agents (Chrome, Firefox, Edge — versions récentes)
- Acceptez les headers
accept-language: en-US,en;q=0.9et les cookies Cloudflare
Patterns de scraping pour la découverte de niche
La recherche de niche sur Etsy repose sur trois métriques clés : les termes de recherche tendance, le nombre de vendeurs par niche et le prix moyen.
Termes de recherche tendance
Etsy expose ses suggestions de recherche via l'endpoint d'autocomplétion :
https://www.etsy.com/api/v3/ajax/member/search-suggestions?query=funnyRéponse tronquée :
{
"results": [
{"query": "funny mug", "type": "search_suggestion"},
{"query": "funny t shirt women", "type": "search_suggestion"},
{"query": "funny birthday card", "type": "search_suggestion"},
{"query": "funny cat shirt", "type": "search_suggestion"}
]
}En itérant sur des racines de mots-clés, vous pouvez construire une carte complète des niches tendance. Croisez ces données avec le nombre de résultats pour chaque terme pour estimer la concurrence.
Nombre de vendeurs et prix moyen par niche
Pour chaque terme de recherche, scrapez la première page de résultats et extrayez :
- shop_name unique — pour compter le nombre de vendeurs distincts
- price — pour calculer le prix moyen et médian
- reviews_count — indicateur de volume de ventes
Un ratio suggestions / vendeurs_uniques élevé signale une niche sous-desservie — l'eldorado pour le POD.
Signaux de validation
- Plus de 5 vendeurs avec des centaines d'avis = demande validée
- Prix médian > 20$ pour un mug = marge suffisante pour POD
- Moins de 50 résultats pour un terme avec suggestions = niche émergente
Script Python : scraper les résultats de recherche Etsy avec rotation de proxies
Voici un script complet qui recherche un terme, parse les listing cards, puis visite chaque page de détail pour extraire des métriques supplémentaires.
import requests
from bs4 import BeautifulSoup
import time
import random
import json
# Configuration ProxyHat — proxies résidentiels avec rotation par pays
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",
"Accept": "text/html,application/xhtml+xml",
}
def fetch_search_results(keyword, page=1):
"""Scrape une page de résultats de recherche Etsy."""
url = f"https://www.etsy.com/search?q={keyword.replace(' ', '+')}&page={page}"
resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=30)
resp.raise_for_status()
return resp.text
def parse_listing_cards(html):
"""Extrait les données des listing cards depuis le HTML."""
soup = BeautifulSoup(html, "html.parser")
cards = soup.select(".v2-listing-card")
listings = []
for card in cards:
link = card.select_one("a.listing-link")
title_el = card.select_one(".v2-listing-card__info .title")
price_el = card.select_one(".currency-value")
if not link:
continue
href = link.get("href", "")
listing_id = href.split("/listing/")[-1].split("/")[0] if "/listing/" in href else ""
listings.append({
"listing_id": listing_id,
"title": title_el.get_text(strip=True) if title_el else "",
"price": price_el.get_text(strip=True) if price_el else "",
"url": f"https://www.etsy.com{href}" if href.startswith("/") else href,
})
return listings
def fetch_listing_detail(listing_url, session_id):
"""Scrape les détails d'un listing avec une session proxy sticky."""
# Session sticky pour garder la même IP pendant la session
proxy = f"http://user-session-{session_id}-country-US:PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy, "https": proxy}
resp = requests.get(listing_url, headers=HEADERS, proxies=proxies, timeout=30)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
# Extraire le JSON-LD pour les métadonnées structurées
ld_json = soup.find("script", type="application/ld+json")
data = json.loads(ld_json.string) if ld_json else {}
# Badge ventes du shop
sales_badge = soup.select_one(".shop-sales-badge")
sales_text = sales_badge.get_text(strip=True) if sales_badge else ""
# Nombre de favoris
fav_el = soup.select_one("[data-favorite-count]")
fav_count = fav_el.get("data-favorite-count", "0") if fav_el else "0"
return {
"name": data.get("name", ""),
"price": data.get("offers", {}).get("price", ""),
"description": data.get("description", "")[:200],
"sales_badge": sales_text,
"favorites": fav_count,
}
# --- Exécution ---
keyword = "funny cat mug"
print(f"Recherche : {keyword}")
html = fetch_search_results(keyword, page=1)
listings = parse_listing_cards(html)
print(f"{len(listings)} listings trouvés")
for i, listing in enumerate(listings[:5]):
time.sleep(random.uniform(2, 5)) # Délai aléatoire anti-détection
detail = fetch_listing_detail(listing["url"], session_id=f"etsy{i}")
print(json.dumps({**listing, **detail}, indent=2, ensure_ascii=False))Points clés de ce script :
- Rotation d'IP via
user-country-USpour les recherches,user-session-{id}pour les sessions sticky sur les pages de détail - Délai aléatoire de 2-5 secondes entre chaque requête
- JSON-LD comme source primaire de données structurées — plus fiable que le parsing CSS
- Fallback CSS pour les données non présentes dans le JSON-LD (badge ventes, favoris)
Analyser les shops Etsy : listings, ventes et avis
Les pages de shop Etsy sont une mine d'or pour la veille concurrentielle. Voici un script dédié :
import requests
from bs4 import BeautifulSoup
import re
import json
PROXY_URL = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY_URL, "https": PROXY_URL}
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
def scrape_shop(shop_name):
"""Scrape les métriques clés d'un shop Etsy."""
url = f"https://www.etsy.com/shop/{shop_name}"
resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=30)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
# Nombre de listings
listing_count_el = soup.select_one(".shop-info-listing-count")
listing_count = listing_count_el.get_text(strip=True) if listing_count_el else "N/A"
# Badge ventes — Etsy affiche "X Sales" ou "X,XXX Sales"
sales_el = soup.select_one(".shop-sales")
sales_raw = sales_el.get_text(strip=True) if sales_el else "0"
sales_number = int(re.sub(r"[^0-9]", "", sales_raw) or "0")
# Note moyenne et nombre d'avis
review_el = soup.select_one(".shop-review-rating")
rating = review_el.get_text(strip=True) if review_el else "N/A"
review_count_el = soup.select_one(".shop-review-count")
review_count = review_count_el.get_text(strip=True) if review_count_el else "0"
# Localisation
location_el = soup.select_one(".shop-location")
location = location_el.get_text(strip=True) if location_el else "N/A"
return {
"shop_name": shop_name,
"listing_count": listing_count,
"sales_approx": sales_number,
"rating": rating,
"review_count": review_count,
"location": location,
}
# Exemple d'usage
result = scrape_shop("ExampleShopName")
print(json.dumps(result, indent=2))Métriques à calculer pour chaque shop
- Ventes par listing =
sales_approx / listing_count— un ratio élevé indique des produits performants - Densité d'avis =
review_count / sales_approx— un ratio faible (~5-10%) est normal sur Etsy - Concentration de niche — si un shop avec 500 ventes ne vend que des mugs chat, la niche est validée
Comparer les approches de scraping
| Approche | Avantages | Inconvénients | Meilleur cas d'usage |
|---|---|---|---|
| API interne Etsy | Données structurées, pas de parsing DOM | Rate limit agressif, tokens instables | Recherche rapide de mots-clés |
| HTML scraping (BeautifulSoup) | Robuste, selecteurs stables, facile à déboguer | Parsing plus lent, structure pouvant changer | Recherche de niche complète |
| HTML scraping (headless browser) | Gère le JS de Cloudflare, rendu complet | Très lent, coûteux en ressources | Quand Cloudflare bloque les requêtes simples |
| API Etsy Open (officielle) | Légal, documenté, stable | Accès limité, clé API nécessaire, pas de données de ventes | Intégrations d'apps partenaires |
Pour la plupart des équipes POD, l'approche HTML scraping + proxies résidentiels offre le meilleur rapport coût/efficacité.
Scraping d'autocomplétion à grande échelle pour la recherche de niche
Pour cartographier les niches, vous devez interroger l'autocomplétion Etsy avec des centaines de racines de mots-clés. Voici un script optimisé avec rotation de proxies :
import requests
import time
import random
import json
# Liste de racines de mots-clés à explorer
KEYWORD_ROOTS = [
"funny", "vintage", "custom", "personalized", "aesthetic",
"minimalist", "boho", "cottagecore", "y2k", "cottage",
"plant", "cat", "dog", "gaming", "coder", "nurse",
"teacher", "mom", "dad", "bridesmaid",
]
def fetch_suggestions(root, proxy_session_id):
"""Interroge l'autocomplétion Etsy avec rotation de session proxy."""
proxy = f"http://user-session-{proxy_session_id}-country-US:PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy, "https": proxy}
url = f"https://www.etsy.com/api/v3/ajax/member/search-suggestions?query={root}"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/125.0.0.0",
"Accept": "application/json",
"Referer": "https://www.etsy.com/",
}
try:
resp = requests.get(url, headers=headers, proxies=proxies, timeout=15)
if resp.status_code == 200:
return resp.json().get("results", [])
else:
print(f" Erreur {resp.status_code} pour '{root}'")
return []
except Exception as e:
print(f" Exception pour '{root}': {e}")
return []
# Collecte des suggestions
all_suggestions = []
for i, root in enumerate(KEYWORD_ROOTS):
# Rotation de session toutes les 10 requêtes
session_id = f"etsy-sug-{i // 10}"
results = fetch_suggestions(root, session_id)
for r in results:
all_suggestions.append(r.get("query", ""))
print(f"{root}: {len(results)} suggestions")
time.sleep(random.uniform(1, 3))
# Dédoublonnage et tri
unique = sorted(set(all_suggestions))
print(f"\nTotal suggestions uniques : {len(unique)}")
print(json.dumps(unique[:20], indent=2))Ce script vous donne une liste de centaines de termes de recherche tendance. L'étape suivante est de scorer chaque terme en scrapant le nombre de résultats et le nombre de vendeurs uniques — en utilisant le premier script de ce guide.
Éthique : les vendeurs Etsy sont des petites entreprises
C'est le point le plus important de ce guide. Les vendeurs Etsy sont majoritairement des petites entreprises et des créateurs indépendants.
Règles éthiques pour le scraping Etsy :
- Scrapez pour la recherche, pas pour la copie. Analyser les tendances de prix et les niches populaires est légitime. Copier des designs originaux et les reproduire en POD ne l'est pas.
- Respectez robots.txt. Etsy autorise certains chemins et en bloque d'autres. Vérifiez
https://www.etsy.com/robots.txtrégulièrement. - Ne surchargez pas le serveur. Un délai de 2-5 secondes entre les requêtes est un minimum. 5-10 secondes est plus responsable.
- Ne scrapez pas les images pour les réutiliser. Les photos de produits sont la propriété des vendeurs.
- Conformez-vous au GDPR et au CCPA. Ne stockez pas de données personnelles de vendeurs (noms, adresses) sans nécessité.
- Utilisez les données pour l'inspiration, pas la contrefaçon. Si un vendeur a du succès avec un concept, inspirez-vous-en pour créer votre propre version originale.
La ligne rouge : si votre scraping conduit à des produits qui sont des copies carbone de designs existants, vous avez franchi la limite éthique. La recherche de niche doit vous aider à identifier des opportunités, pas à voler le travail d'autres créateurs.
Bonnes pratiques pour un scraping Etsy fiable et durable
Gestion des proxies
- Utilisez des proxies résidentiels rotatifs — les IPs datacenter sont immédiatement détectées
- Ciblez les États-Unis comme pays (
country-US) — Etsy sert plus de résultats aux IPs américaines - Rottez les sessions toutes les 30-50 requêtes pour éviter l'accumulation de signaux de bot
- Pour les sessions longues (scraping de shop complet), utilisez des sessions sticky de 10-30 minutes
Gestion des erreurs
- Implémentez un retry avec backoff exponentiel : 5s, 15s, 45s entre les tentatives
- Sur un challenge Cloudflare, attendez 15 minutes minimum avant de réessayer
- Enregistrez les IDs de listing déjà scrapés pour ne pas re-scrape en cas d'interruption
- Utilisez un circuit breaker : si plus de 5 erreurs consécutives, pause de 30 minutes
Optimisation des performances
- Scrapez les pages de recherche en parallèle (5-10 threads max) avec des IPs différentes
- Scrapez les pages de détail en séquentiel avec délai — c'est là que Cloudflare est le plus sensible
- Extrayez le maximum de données du JSON-LD avant de parser le DOM — plus rapide et plus fiable
- Mettez en cache les résultats de recherche pendant 24h — les données changent peu
Points clés à retenir
Key Takeaways :
- Etsy combine Cloudflare + rate limits internes — les proxies résidentiels sont obligatoires pour tout scraping sérieux
- Privilégiez le HTML scraping avec JSON-LD pour la robustesse ; l'API interne pour les recherches rapides
- La recherche de niche repose sur trois métriques : termes tendance (autocomplétion), concurrence (vendeurs uniques), prix moyen
- Les badges "X Sales" sur les shops sont des estimations approximatives — utiles pour la veille, pas pour du reporting exact
- Rottez les IPs résidentielles toutes les 30-50 requêtes et maintenez des délais de 2-5 secondes
- Éthique avant tout : scrapez pour la recherche de niche, ne copiez jamais les designs des créateurs
Pour des proxies résidentiels optimisés pour le scraping Etsy, explorez les offres ProxyHat — rotation par pays, sessions sticky, et un pool d'IPs résidentielles dans plus de 190 pays. Consultez aussi notre guide sur le web scraping pour des stratégies avancées de rotation et de gestion d'erreurs.






