Se vendi Print-on-Demand o costruisci tool di ricerca marketplace, Etsy è una miniera d'oro di dati: trend stagionali, pricing per categoria, densità competitiva. Ma Etsy non offre un'API pubblica per la ricerca di listing. La scelta è semplice: usi l'API Open (solo per i tuoi shop) o fai scrape dell'HTML pubblico. Questa guida copre la seconda opzione — con i selettori, i limiti di rate e le strategie proxy che contano davvero.
Struttura di Etsy: Cosa C'è da Scrape
Etsy ha quattro superfici principali per un niche researcher:
Pagine di Ricerca (Search)
L'URL base è https://www.etsy.com/search?q=QUERY&ref=search_bar. Ogni pagina mostra ~48 listing card. I parametri utili:
q=— termine di ricercapage=— paginazione (max ~250 pagine visibili)order=—highest_reviewed,price_asc,price_desc,most_recentlocation_query=— filtro geograficomin_price=/max_price=— range di prezzo
Le listing card nella pagina di ricerca contengono: titolo, prezzo, immagine thumbnail, nome shop, numero recensioni, badge "Bestseller". Il markup è in React-rendered HTML — i selettori cambiano occasionalmente, ma i data attribute restano stabili.
Pagine Listing Dettaglio
URL: https://www.etsy.com/listing/LISTING_ID/TITLE_SLUG. Contengono: descrizione completa, varianti, prezzo per variante, immagini ad alta risoluzione, recensioni, info spedizione, sezione shop.
Pagine Shop
URL: https://www.etsy.com/shop/SHOP_NAME. Espongono: conteggio listing, badge "x sales" (approssimato), anno di creazione, recensioni aggregate, location.
Albero Categorie
Etsy espone le categorie via https://www.etsy.com/c/CATEGORY_SLUG. Le sottocategorie sono navigabili. Per mappare l'albero completo, punta alla navigazione laterale o al JSON embed in window.__initial_state__ dentro il tag <script> della homepage.
Anti-Bot di Etsy: Cloudflare e Rate Limits
Etsy usa Cloudflare come WAF/CDN frontale. Questo significa:
- Challenge JS per richieste sospette (browser headless senza fingerprint adeguata)
- Rate limiting lato Cloudflare — tipicamente ~200 richieste/min per IP prima di un soft-block 429
- Hard block (403 con challenge) se il pattern di accesso sembra bot (troppo veloce, troppi page-view sequenziali, user-agent incoerente)
Etsy ha anche rate limiting interno all'applicazione. Se fetchi troppe pagine di ricerca consecutive per la stessa query, vedrai un interstitial CAPTCHA o un redirect alla homepage.
Regola empirica: con IP residenziali e delay 2-4s tra richieste, puoi raccogliere ~800-1000 pagine/ora senza trigger. Con IP datacenter, il limite scende a ~100-200 pagine/ora prima del ban.
Per questo motivo, i proxy residenziali sono la scelta corretta per scrape Etsy. Ruotano IP reali di ISP, Cloudflare non li marca come bot. Un proxy residenziale con rotazione per-request è lo schema consigliato.
Pattern di Scrape per Niche Discovery
L'obiettivo del niche researcher è rispondere a tre domande:
- Quanto è popolare la domanda? → numero di listing per keyword, presenza badge "Bestseller"
- Quanto è affollata l'offerta? → numero di seller unici per keyword
- Qual è il range di prezzo? → prezzo medio, mediano, deviazione
Flusso di lavoro consigliato
- Raccogli una lista di keyword candidate (da Etsy search suggestions, Google Trends, o strumenti come eRank)
- Per ogni keyword, fetcha le prime 3-5 pagine di ricerca Etsy
- Parse le listing card: estrai shop_name, price, review_count, bestseller_badge
- Calcola: listing_count (totale indicato), unique_sellers, avg_price, bestseller_ratio
- Per le niche promettenti, drill-down nelle pagine shop dei top seller
Un esempio di metrica composita utile: Opportunity Score = (review_count_medio × bestseller_ratio) / unique_sellers. Score alto = domanda elevata, poca concorrenza.
Esempio Python: Ricerca → Listing Card → Dettaglio con Proxy Residenziali
Il seguente script mostra il flusso completo: fetch pagine di ricerca, parse delle listing card, poi visita delle pagine dettaglio con rotazione IP residenziale tramite ProxyHat.
import requests
from bs4 import BeautifulSoup
import time
import json
import random
# Configurazione ProxyHat — proxy residenziale con rotazione per-request
PROXY_USER = "user-country-US"
PROXY_PASS = "your_password"
PROXY_HTTP = f"http://{PROXY_USER}:{PROXY_PASS}@gate.proxyhat.com:8080"
proxies = {"http": PROXY_HTTP, "https": PROXY_HTTP}
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(keyword, page=1):
"""Fetcha una pagina di ricerca Etsy."""
url = "https://www.etsy.com/search"
params = {"q": keyword, "page": page}
resp = requests.get(
url, params=params, headers=HEADERS,
proxies=proxies, timeout=30
)
resp.raise_for_status()
time.sleep(random.uniform(2.0, 4.0))
return resp.text
def parse_search_cards(html):
"""Estrae dati dalle listing card nella pagina di ricerca."""
soup = BeautifulSoup(html, "html.parser")
listings = []
# Selettore principale — aggiornare se Etsy cambia il markup
cards = soup.select("div[data-search-results] li")
for card in cards:
title_el = card.select_one("h3, [class*='title']")
price_el = card.select_one("span[class*='currency-value'], span[class*='price']")
shop_el = card.select_one("a[class*='shop'], [class*='seller']")
reviews_el = card.select_one("span[class*='review'], [class*='rating-count']")
bestseller = bool(card.select_one("[class*='bestseller'], .wt-badge"))
listing = {
"title": title_el.get_text(strip=True) if title_el else None,
"price": price_el.get_text(strip=True) if price_el else None,
"shop": shop_el.get_text(strip=True) if shop_el else None,
"reviews": reviews_el.get_text(strip=True) if reviews_el else None,
"bestseller": bestseller,
}
# Estrai link al listing per il drill-down
link_el = card.select_one("a[href*='/listing/']")
if link_el:
listing["url"] = link_el.get("href")
listings.append(listing)
return listings
Il secondo step è visitare le pagine dettaglio per estrarre descrizione, varianti e immagini:
def fetch_listing_detail(listing_url):
"""Fetcha la pagina dettaglio di un singolo listing."""
# Ogni richiesta usa un IP residenziale diverso via ProxyHat
session = requests.Session()
session.proxies = proxies
session.headers.update(HEADERS)
resp = session.get(listing_url, timeout=30)
resp.raise_for_status()
time.sleep(random.uniform(2.5, 5.0))
return resp.text
def parse_listing_detail(html):
"""Estrae dati dalla pagina dettaglio listing."""
soup = BeautifulSoup(html, "html.parser")
# Descrizione
desc_el = soup.select_one("#description, [data-appears-component-name='description']")
description = desc_el.get_text(strip=True) if desc_el else None
# Prezzi varianti
variants = []
for opt in soup.select("[data-variation-id], .variation-select option"):
label = opt.get_text(strip=True)
variants.append(label)
# Immagini
images = []
for img in soup.select("img[data-src-zoom], .image-carousel img"):
src = img.get("data-src-zoom") or img.get("src")
if src:
images.append(src)
# Sezione shop (per drill-down su shop analytics)
shop_link = soup.select_one("a[href*='/shop/']")
shop_name = None
if shop_link:
href = shop_link.get("href", "")
shop_name = href.rstrip("/").split("/shop/")[-1]
return {
"description": description,
"variants": variants,
"images": images,
"shop_name": shop_name,
}
Il loop principale per la niche research:
def niche_research(keyword, max_pages=3):
"""Flusso completo: ricerca → card → dettaglio per niche research."""
all_listings = []
# Step 1: raccogli listing card dalle pagine di ricerca
for page in range(1, max_pages + 1):
html = fetch_search(keyword, page=page)
cards = parse_search_cards(html)
if not cards:
print(f"Nessuna card trovata a pagina {page} — possibile blocco")
break
all_listings.extend(cards)
print(f"Pagina {page}: {len(cards)} listing trovati")
# Step 2: calcola metriche aggregate
shops = set(l["shop"] for l in all_listings if l.get("shop"))
prices = []
for l in all_listings:
if l.get("price"):
try:
# Estrae numero dal testo prezzo (es. "$12.50")
price_num = float(
l["price"].replace("$", "").replace("€", "")
.replace(",", "").strip().split()[0]
)
prices.append(price_num)
except ValueError:
pass
bestseller_count = sum(1 for l in all_listings if l.get("bestseller"))
print(f"\n--- Metriche Niche: '{keyword}' ---")
print(f"Listing totali: {len(all_listings)}")
print(f"Seller unici: {len(shops)}")
print(f"Prezzo medio: ${sum(prices)/len(prices):.2f}" if prices else "N/D")
print(f"Bestseller ratio: {bestseller_count/len(all_listings):.1%}")
# Step 3: drill-down sui primi 5 listing per dettaglio
for listing in all_listings[:5]:
if listing.get("url"):
detail_html = fetch_listing_detail(listing["url"])
detail = parse_listing_detail(detail_html)
listing["detail"] = detail
print(f" Dettaglio: {listing['title'][:50]}...")
return all_listings
# Esecuzione
results = niche_research("custom pet portrait", max_pages=3)
# Salvataggio
with open("etsy_niche_data.json", "w") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
Shop Analytics: Estrarre Metriche dai Seller
Etsy espone dati pubblici sugli shop che sono preziosi per competitive analysis:
- Conteggio listing: visibile nella sidebar dello shop
- Badge "x sales": Etsy mostra un numero approssimato (es. "1,234 Sales") — è un ordine di grandezza, non il numero esatto
- Recensioni aggregate: numero totale e media stelle
- Anno di creazione: "On Etsy since 2019"
- Location: paese e talvolta città
Per scrapeare questi dati da una pagina shop:
def parse_shop_page(html):
"""Estrae metriche pubbliche dalla pagina shop Etsy."""
soup = BeautifulSoup(html, "html.parser")
# Sales badge — es. "1,234 Sales"
sales = None
sales_el = soup.select_one("[class*='sales'], [data-sales]");
if sales_el:
sales_text = sales_el.get_text(strip=True)
# Parse numerico
import re
match = re.search(r'([\d,]+)\s*Sales', sales_text, re.I)
if match:
sales = int(match.group(1).replace(",", ""))
# Conteggio listing nella sidebar
listing_count = None
count_el = soup.select_one("[class*='listing-count'], .shop-info span")
if count_el:
match = re.search(r'(\d+)', count_el.get_text())
if match:
listing_count = int(match.group(1))
# Recensioni
review_count = None
avg_rating = None
rev_el = soup.select_one("[class*='review-count'], .reviews-summary")
if rev_el:
match = re.search(r'(\d+)', rev_el.get_text())
if match:
review_count = int(match.group(1))
star_el = soup.select_one("[class*='star-rating'], [aria-label*='star']")
if star_el:
match = re.search(r'(\d+\.?\d*)', star_el.get("aria-label", "") or star_el.get_text())
if match:
avg_rating = float(match.group(1))
# Location
loc_el = soup.select_one("[class*='location'], .shop-location")
location = loc_el.get_text(strip=True) if loc_el else None
return {
"sales": sales,
"listing_count": listing_count,
"review_count": review_count,
"avg_rating": avg_rating,
"location": location,
}
Esempio di risposta (truncata)
Dopo il parse di uno shop POD che vende mug personalizzati, otterresti qualcosa di simile:
{
"sales": 12450,
"listing_count": 387,
"review_count": 8921,
"avg_rating": 4.8,
"location": "Austin, TX"
}
Con questi dati puoi costruire una tabella comparativa dei top seller in una niche:
| Shop | Sales (appross.) | Listing | Reviews | Avg Rating | Location |
|---|---|---|---|---|---|
| CustomMugStudio | 12,450 | 387 | 8,921 | 4.8 | Austin, TX |
| MugArtPrints | 8,200 | 210 | 5,430 | 4.7 | London, UK |
| PrintYourPet | 5,100 | 142 | 3,200 | 4.6 | Toronto, CA |
| MugifyDesigns | 2,800 | 95 | 1,870 | 4.5 | Berlin, DE |
Questa tabella ti dice rapidamente: la niche è affollata al top (un seller con 12k+ sales), ma c'è spazio nel mid-tier. La location US domina — se puoi offrire spedizione più veloce in Europa, c'è un gap.
Proxy per Etsy: Perché Residenziali e Come Configurarli
La scelta del proxy è determinante per Etsy. Ecco il confronto:
| Tipo Proxy | Success Rate Etsy | Velocità | Costo | Uso Consigliato |
|---|---|---|---|---|
| Datacenter | ~40-60% | Veloce | Basso | Testing, poche pagine |
| Residenziale (rotante) | ~90-97% | Medio | Medio | Scraping produzione |
| Mobile | ~95-99% | Lento | Alto | Anti-bot estremo |
Per la maggior parte dei casi di Etsy niche research, i proxy residenziali con rotazione per-request sono il sweet spot. Configurazione con ProxyHat:
# Rotazione per-request — ogni richiesta ottiene un IP diverso
PROXY = "http://user-country-US:your_password@gate.proxyhat.com:8080"
# Sessione sticky — stesso IP per 10-15 minuti (utile per drill-down multi-pagina)
PROXY_STICKY = "http://user-country-US-session-abc123:your_password@gate.proxyhat.com:8080"
# Geo-targeting — simula traffico dal paese target
PROXY_UK = "http://user-country-GB:your_password@gate.proxyhat.com:8080"
PROXY_DE = "http://user-country-DE:your_password@gate.proxyhat.com:8080"
# SOCKS5 (per tool che lo richiedono)
PROXY_SOCKS = "socks5://user-country-US:your_password@gate.proxyhat.com:1080"
Usa la rotazione per-request per le pagine di ricerca (accesso sequenziale a tante pagine). Usa le sessioni sticky quando navighi dentro lo stesso shop — così il tuo traffico sembra quello di un utente reale che esplora uno store.
Strategia di rate limiting consigliata
- Ricerca (search pages): 1 richiesta ogni 2-4 secondi per IP
- Listing dettaglio: 1 richiesta ogni 3-5 secondi per IP
- Shop pages: 1 richiesta ogni 3-5 secondi per IP
- Massimo giornaliero per sicurezza: ~10.000 pagine/sessione di scraping, poi pausa di alcune ore
Considerazioni Etiche: I Seller di Etsy Sono Piccole Imprese
Questa è la parte più importante di tutta la guida. Etsy non è Amazon. La maggior parte dei seller sono piccoli artigiani, artisti indipendenti, familiari che si sono costruiti un business con sacrificio.
Cosa è accettabile
- Raccogliere dati di pricing e trend per posizionare i tuoi prodotti nel mercato
- Analizzare la densità competitiva per decidere se entrare in una niche
- Monitorare recensioni aggregate per migliorare la qualità dei tuoi prodotti
- Raccogliere keyword e tag per ottimizzare le tue listing
Cosa NON è accettabile
- Copiare design — se trovi un pattern che vende, non riprodurlo identico. Ispirati, ma crea qualcosa di tuo
- Scrapeare immagini per riutilizzarle — è violazione di copyright
- Scrapeare descrizioni per usarle come template — è plagio
- DDoSare lo shop di un concorrente con richieste massive
La regola d'oro: se il dato ti aiuta a prendere decisioni strategiche, è ricerca legittima. Se lo usi per replicare il lavoro di qualcun altro, stai oltrepassando il limite.
Rispetta anche i termini di servizio di Etsy. Il loro ToS proibisce esplicitamente lo scraping automatizzato senza autorizzazione. Se il tuo uso è limitato alla ricerca e non sovraccarichi i loro server, il rischio pratico è basso — ma è bene esserne consapevoli.
Da un punto di vista legale, considera anche il GDPR se operi in Europa: i dati dei seller (nome, location) sono dati personali. Non memorizzarli più del necessario e non condividerli con terzi senza base legale.
Key Takeaways
- Etsy non ha API pubblica per la ricerca — scraping HTML è l'unica via per niche research automatizzata
- Cloudflare + rate limit interno rendono i proxy residenziali obbligatori per qualsiasi operazione oltre il testing basilare
- Il flusso produttivo è: search → parse card → drill-down listing → drill-down shop
- Le metriche chiave per niche research: listing count, unique sellers, avg price, bestseller ratio, review density
- Il badge "x sales" di Etsy è approssimato ma utile per confronti relativi tra seller
- Usa rotazione per-request per le search pages e sessioni sticky per l'esplorazione degli shop
- Rispetta i seller — ricerca sì, copia no. I dati sono per decisioni strategiche, non per replicare il lavoro altrui
Se sei pronto a iniziare con proxy residenziali ottimizzati per Etsy, dai un'occhiata alle opzioni di pricing di ProxyHat — i piani partono da traffico basso, perfetti per niche research periodica.






