Scrapear Etsy: ¿API pública o HTML?
Si trabajas en print-on-demand (POD) o herramientas de investigación de mercado, Etsy es una mina de oro: millones de listings, datos de ventas visibles y categorías que revelan tendencias. Pero no existe una API pública oficial para buscar listings o extraer analytics de shops. Tienes dos caminos:
- Open API (v3) — limitada a sellers autenticados vía OAuth. Sirve para gestionar tu propia tienda, no para investigación masiva.
- Scraping HTML — la única forma real de obtener datos de búsqueda, precios, ventas y reseñas a escala. Es lo que cubre esta guía.
El trade-off es claro: HTML te da acceso completo, pero te enfrentas a Cloudflare, rate limits internos y estructura que cambia. Necesitas proxies residenciales y código robusto. Vamos al grano.
Estructura de Etsy: search, listings, shops y categorías
Entender la anatomía de Etsy es el primer paso para scrapear de forma eficiente. Cada tipo de página tiene datos distintos y requiere estrategias diferentes.
Páginas de búsqueda
La URL base de búsqueda:
https://www.etsy.com/search?q=mothers+day+mug&ref=search_bar
Parámetros útiles:
q— término de búsquedapage— paginación (1, 2, 3…)order=date_desc— ordenar por más recientesprice_min=&price_max=— filtro de precioship_to=ES— geolocalización del comprador
Cada página devuelve ~48 listings. Los datos clave están en los listing cards dentro del contenedor <ul class="wt-grid"> o en el JSON embebido en window.__INITIAL_STATE__.
Páginas de listing individual
URL: https://www.etsy.com/listing/{listing_id}/{slug}
Aquí encuentras: título completo, descripción, precio con variaciones, imágenes, reseñas, datos del vendedor, y el badge de ventas.
Páginas de shop
URL: https://www.etsy.com/shop/{shop_name}
Incluye: número total de listings, badge de ventas («X sales»), reseñas agregadas, ubicación del vendedor, y fecha de creación.
Árbol de categorías
Etsy organiza productos en categorías como /c/jewelry, /c/clothing, etc. Cada categoría tiene subcategorías accesibles vía el sidebar. Puedes scrapear el árbol de categorías desde:
https://www.etsy.com/c/{category}?ref=catnav_{id}
Para investigación de nichos, el árbol de categorías te permite mapear mercados completos en lugar de ir término a término.
Anti-bot de Etsy: Cloudflare, rate limits y por qué necesitas proxies residenciales
Etsy usa Cloudflare como WAF principal. Esto significa:
- Desafíos JavaScript (5 seconds) que bloquean requests sin browser real.
- Fingerprinting de TLS y headers HTTP.
- Rate limiting interno: aproximadamente ~100-150 requests/min desde una misma IP antes de recibir HTTP 429 o un challenge de Cloudflare.
- Baneos temporales de IP (4-12 horas) tras patrones de acceso repetitivos.
Datacenter vs residenciales vs móviles para Etsy
| Tipo de proxy | Probabilidad de bloqueo | Velocidad | Recomendado para |
|---|---|---|---|
| Datacenter | Alta — Cloudflare detecta rangos de DC | Rápido | No recomendado para Etsy |
| Residencial | Baja — IPs de ISPs reales | Media | Scraping de búsqueda y listings |
| Móvil | Muy baja — IPs de carriers | Lenta | Accesos de alta sensibilidad |
Para Etsy, proxies residenciales con rotación por request son el punto dulce. Los datacenter los detecta Cloudflare en minutos. Los móviles son overkill salvo que necesites evadir challenges persistentes.
Patrones de scraping para descubrimiento de nichos
El objetivo no es scrapear listings al azar. Es extraer datos que te permitan tomar decisiones de nicho. Estos son los patrones más útiles para equipos POD:
1. Términos de búsqueda en tendencia
Etsy expone sugerencias de búsqueda en el endpoint de autocompletado:
https://www.etsy.com/api/v3/ajax/member/suggestions?query=mothers+day
Este endpoint devuelve JSON con sugerencias relacionadas. Scrapeando prefijos populares (a-z), puedes construir un mapa de términos en tendencia.
2. Conteo de vendedores por nicho
Busca un término y extrae el contador de resultados: <span class="wt-text-caption">XX,XXX results. Un nicho con muchas búsquedas pero pocos vendedores es una oportunidad. Un nicho saturado (>100k results) probablemente no valga la pena para POD nuevo.
3. Precios promedio por nicho
De los listing cards en la página de búsqueda, extrae precios con:
// Selector CSS para precios en listing cards
span.wt-text-title-01 /* precio principal */
span.wt-text-caption /* precio con envío */
Con 3-5 páginas de resultados puedes calcular el precio medio, mediano y la dispersión. Esto te dice si un nicho compite en precio o en valor percibido.
4. Ratio de ventas por listing
En la página de detalle, el badge «X sales» del vendedor + el número de reseñas del listing te dan una aproximación de conversión. Listings con muchas reseñas relativas a su antigüedad = producto validado.
Ejemplo en Python: búsqueda → listing cards → detalle con rotación de proxies
Vamos a construir un scraper funcional que: (1) busca un término en Etsy, (2) extrae listings de la página de resultados, (3) visita cada listing para obtener datos detallados, y (4) rota IPs residenciales en cada request.
Configuración del proxy residencial
import requests
from bs4 import BeautifulSoup
import json
import time
import random
# Configuración ProxyHat — proxies residenciales
PROXY_USER = "user-country-US" # IP residencial de EE.UU.
PROXY_PASS = "tu_password_aqui"
PROXY_HTTP = f"http://{PROXY_USER}:{PROXY_PASS}@gate.proxyhat.com:8080"
PROXY_SOCKS5 = f"socks5://{PROXY_USER}:{PROXY_PASS}@gate.proxyhat.com:1080"
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": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
}
Paso 1: Scrapear página de búsqueda
def scrape_etsy_search(query: str, page: int = 1) -> list[dict]:
"""Extrae listing cards de una página de búsqueda de Etsy."""
url = "https://www.etsy.com/search"
params = {"q": query, "page": page}
# Rotar sesión por request — nueva IP residencial
session_id = f"etsy-search-{query}-{page}-{int(time.time())}"
dynamic_user = f"user-country-US-session-{session_id}"
proxy_url = f"http://{dynamic_user}:{PROXY_PASS}@gate.proxyhat.com:8080"
current_proxies = {"http": proxy_url, "https": proxy_url}
resp = requests.get(
url, params=params, headers=headers,
proxies=current_proxies, timeout=30
)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
listings = []
# Intentar extraer del JSON embebido primero
script_tags = soup.find_all("script", type="application/json")
# Fallback: scraping de HTML cards
cards = soup.select("div.v2-listing-card")
for card in cards:
link = card.select_one("a.listing-link")
title_el = card.select_one("h3")
price_el = card.select_one("span.wt-text-title-01")
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,
"url": href,
"title": title_el.get_text(strip=True) if title_el else "",
"price": price_el.get_text(strip=True) if price_el else "",
})
return listings
# Uso
results = scrape_etsy_search("mothers day mug", page=1)
print(json.dumps(results[:3], indent=2))
Respuesta de ejemplo (truncada):
[
{
"listing_id": "1689234021",
"url": "https://www.etsy.com/listing/1689234021/...",
"title": "Personalized Mothers Day Mug, Custom Name...",
"price": "$14.99"
},
{
"listing_id": "1723456789",
"url": "https://www.etsy.com/listing/1723456789/...",
"title": "Mom Coffee Mug, Gift for Mom from...",
"price": "$12.50"
}
]
Paso 2: Scrapear detalle de cada listing
def scrape_etsy_listing(listing_url: str, listing_id: str) -> dict:
"""Extrae datos detallados de un listing individual de Etsy."""
session_id = f"etsy-detail-{listing_id}-{int(time.time())}"
dynamic_user = f"user-country-US-session-{session_id}"
proxy_url = f"http://{dynamic_user}:{PROXY_PASS}@gate.proxyhat.com:8080"
current_proxies = {"http": proxy_url, "https": proxy_url}
resp = requests.get(
listing_url, headers=headers,
proxies=current_proxies, timeout=30
)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
# Título completo
title = soup.select_one("h1[data-testid='listing-title']")
title_text = title.get_text(strip=True) if title else ""
# Precio (incluye variaciones)
price_el = soup.select_one("p[data-testid='listing-price']")
price = price_el.get_text(strip=True) if price_el else ""
# Badge de ventas del vendedor: "1234 sales"
sales_badge = soup.select_one("a[href*='/shop/'] span")
sales_text = ""
if sales_badge:
import re
match = re.search(r'(\d[\d,]*)\s+sales', sales_badge.get_text())
if match:
sales_text = match.group(1)
# Número de reseñas del listing
reviews_el = soup.select_one("button[data-testid='reviews-header']")
review_count = 0
if reviews_el:
match = re.search(r'(\d+)', reviews_el.get_text())
if match:
review_count = int(match.group(1))
# Imágenes
images = [img.get("src", "") for img in soup.select("img[data-testid='listing-image']")]
return {
"listing_id": listing_id,
"title": title_text,
"price": price,
"seller_sales": sales_text,
"review_count": review_count,
"image_count": len(images),
}
# Pipeline completo para un nicho
def research_niche(query: str, max_pages: int = 3):
all_listings = []
for page in range(1, max_pages + 1):
listings = scrape_etsy_search(query, page=page)
all_listings.extend(listings)
time.sleep(random.uniform(2, 5)) # Rate limiting responsable
# Scrapear detalles de los primeros 10 listings
detailed = []
for listing in all_listings[:10]:
if listing["listing_id"]:
detail = scrape_etsy_listing(listing["url"], listing["listing_id"])
detailed.append(detail)
time.sleep(random.uniform(3, 7))
return detailed
niche_data = research_niche("mothers day mug", max_pages=2)
print(json.dumps(niche_data[:2], indent=2))
Analytics de shop: listings, ventas y reseñas
Para investigación competitiva, necesitas entender no solo productos individuales sino shops completas. Etsy expone datos valiosos en cada página de tienda:
Datos disponibles en la página de shop
- Número de listings: visible en el sidebar como «X items».
- Ventas totales: el badge «X sales» junto al nombre de la shop. Etsy redondea, pero te da una aproximación suficiente.
- Calificación promedio: estrellas + número de reseñas.
- Fecha de creación: «On Etsy since 2018» — te indica madurez.
- Ubicación: país y ciudad del vendedor.
Scraper de shop
def scrape_etsy_shop(shop_name: str) -> dict:
"""Extrae métricas clave de una shop de Etsy."""
session_id = f"etsy-shop-{shop_name}-{int(time.time())}"
dynamic_user = f"user-country-US-session-{session_id}"
proxy_url = f"http://{dynamic_user}:{PROXY_PASS}@gate.proxyhat.com:8080"
current_proxies = {"http": proxy_url, "https": proxy_url}
url = f"https://www.etsy.com/shop/{shop_name}"
resp = requests.get(url, headers=headers, proxies=current_proxies, timeout=30)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
# Ventas
sales = ""
sales_el = soup.select_one("span[data-testid='sales-badge']")
if sales_el:
match = re.search(r'([\d,]+)\s*sale', sales_el.get_text())
if match:
sales = match.group(1)
# Número de listings
listings_count = 0
items_el = soup.select_one("span[data-testid='items-count']")
if items_el:
match = re.search(r'([\d,]+)', items_el.get_text())
if match:
listings_count = int(match.group(1).replace(',', ''))
# Reseñas
reviews = 0
rev_el = soup.select_one("a[href*='/reviews']")
if rev_el:
match = re.search(r'([\d,]+)', rev_el.get_text())
if match:
reviews = int(match.group(1).replace(',', ''))
# Año de creación
year = ""
since_el = soup.select_one(string=re.compile(r'On Etsy since'))
if since_el:
match = re.search(r'(\d{4})', since_el)
if match:
year = match.group(1)
return {
"shop": shop_name,
"total_sales": sales,
"listing_count": listings_count,
"review_count": reviews,
"since_year": year,
}
# Análisis de competidores en un nicho
shops = ["ShopName1", "ShopName2", "ShopName3"]
for shop in shops:
data = scrape_etsy_shop(shop)
print(json.dumps(data, indent=2))
time.sleep(random.uniform(3, 6))
Métricas derivadas que calculan los equipos POD
- Ventas por listing: total_sales / listing_count → indica si la shop vende pocos productos muy exitosos o muchos productos con pocas ventas.
- Ratio reseñas/ventas: review_count / total_sales → típicamente 5-10% en Etsy. Una shop con ratio alto tiene clientes más comprometidos.
- Velocidad de ventas: total_sales / (año_actual - since_year) → ventas anuales promedio. Útil para comparar shops nuevas vs establecidas.
Estrategia de rotación de IPs y rate limiting
Para mantener tu scraper funcionando sin bloqueos, sigue estas reglas prácticas:
- Rotación por request: usa sesiones únicas en ProxyHat (
user-country-US-session-{id}) para obtener una IP residencial nueva en cada request. - Delay entre requests: 2-5 segundos para búsqueda, 3-7 segundos para listings de detalle. Los ráfagas de requests son la causa #1 de baneos.
- Geo-targeting: si investigas el mercado de EE.UU., usa
user-country-US. Para UK,user-country-GB. Los precios y listings varían por ubicación. - Sticky sessions: si necesitas mantener sesión (por ejemplo, para paginación), usa el mismo session ID durante 10-30 minutos.
- Límite de concurrencia: no superes 5 threads simultáneos contra Etsy desde cualquier cuenta de proxy.
Consideraciones éticas: los vendedores de Etsy son pequeños negocios
Esto es fundamental. A diferencia de Amazon o Walmart, la mayoría de vendedores en Etsy son personas individuales y pequeños negocios. Scrapear sus datos conlleva una responsabilidad:
Lo que SÍ es aceptable
- Investigar nichos para identificar oportunidades de mercado.
- Analizar rangos de precios para posicionar tus productos POD.
- Identificar gaps en el mercado (categorías con demanda y poca oferta).
- Monitorear tendencias estacionales (Mother's Day, Halloween, Navidad).
Lo que NO es aceptable
- Copiar diseños, textos de descripciones o imágenes de otros vendedores.
- Scrapear datos de contacto de vendedores para spam o outreach no solicitado.
- Republish listings completos en otras plataformas.
- Scrapear a una velocidad que degrade la experiencia de otros usuarios (DDoS de facto).
Regla de oro: usa los datos para tomar decisiones de negocio, no para replicar el trabajo creativo de otros. La investigación de mercado es legítima; el plagio no lo es.
Además, respeta robots.txt de Etsy y los términos de servicio. Si Etsy te detecta y bloquea, es una señal de que estás excediendo los límites razonables.
Key Takeaways
- Etsy no tiene API pública de búsqueda — el scraping HTML es la única vía para investigación de nichos a escala.
- Cloudflare + rate limits internos (~100-150 req/min por IP) hacen imprescindible el uso de proxies residenciales con rotación por request.
- Los datos más valiosos para POD están en: términos de búsqueda en tendencia, conteo de resultados por nicho, precios promedio, y badges de ventas de shops.
- Usa sesiones únicas en ProxyHat (
user-country-US-session-{id}) para rotar IPs residenciales en cada request. - Calcula métricas derivadas como ventas/listing y ratio reseñas/ventas para priorizar nichos con validación real.
- Los vendedores de Etsy son pequeños negocios — scrapea para investigar, nunca para copiar diseños o contenidos.
¿Listo para empezar tu investigación de nichos en Etsy con proxies residenciales fiables? Explora los planes de ProxyHat y las ubicaciones disponibles para elegir el geo-targeting que necesitas. Para más guías de scraping, consulta nuestro caso de uso de web scraping y de SERP tracking.






