Cómo scrapear Etsy: guía completa de investigación de nichos y proxies residenciales

Aprende a scrapear Etsy para investigación de nichos POD: estructura del sitio, anti-bot de Cloudflare, patrones de scraping con Python y proxies residenciales, y ética con pequeños vendedores.

Cómo scrapear Etsy: guía completa de investigación de nichos y proxies residenciales

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úsqueda
  • page — paginación (1, 2, 3…)
  • order=date_desc — ordenar por más recientes
  • price_min=&price_max= — filtro de precio
  • ship_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 proxyProbabilidad de bloqueoVelocidadRecomendado para
DatacenterAlta — Cloudflare detecta rangos de DCRápidoNo recomendado para Etsy
ResidencialBaja — IPs de ISPs realesMediaScraping de búsqueda y listings
MóvilMuy baja — IPs de carriersLentaAccesos 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.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog