Как скрапить Etsy: руководство по нишевым исследованиям для POD

Прагматичный гайд по скрапингу Etsy для POD-команд: структура сайта, обход Cloudflare, residential-прокси, Python-примеры и этика работы с малым бизнесом.

Как скрапить Etsy: руководство по нишевым исследованиям для POD

Скрапинг Etsy: API или HTML — с чего начать

Etsy предоставляет Open API v3, но он спроектирован для seller-tools и требует OAuth. Эндпоинты вроде /v3/application/shops и /v3/application/listings доступны только через приложение, одобренное Etsy. Для нишевых исследований — поиска трендов, анализа конкурентов, оценки цен — HTML-поверхность остаётся единственным реальным вариантом. API не отдаёт данные о продажах магазинов, не показывает трендовые поисковые запросы и не даёт доступа к дереву категорий. В этом руководстве — структура Etsy, защита от ботов, конкретные паттерны скрапинга и рабочие примеры на Python.

Структура Etsy: что и как скрапить

Страницы поиска

URL поиска: https://www.etsy.com/search?q=mug+design. Результаты загружаются серверным рендерингом — карточки товаров доступны в HTML без JavaScript. Пагинация — параметр &page=2 в URL. Etsy показывает до ~500 страниц результатов, но для нишевых исследований обычно достаточно первых 5–10.

Ключевые CSS-селекторы для страницы поиска:

  • Контейнер результатов: div[data-results]
  • Карточки товаров: div[data-results] .v2-listing-card или [data-listing-id]
  • Название: .v2-listing-card__info h3
  • Цена: .currency-value
  • Рейтинг: .v2-listing-card__info .star-rating
  • Ссылка на товар: a.listing-link (атрибут href)
  • Общее число результатов: .wt-text-gray .wt-mb-xs-0 (текст «X,XXX results»)

Страницы товаров (листинги)

URL листинга: https://www.etsy.com/listing/123456789/item-name. Etsy рендерит ключевые данные через JSON в теге <script type="application/ld+json"> — это самый надёжный способ извлечения структурированных данных, не зависящий от CSS-классов.

Что извлекаем из листинга:

  • Заголовок, описание, цена, валюта: из ld+json блока (@type: Product)
  • Продавец: a[data-shop-id] — имя и ID магазина
  • Количество продаж продавца: текст бейджа «X sales» рядом с именем
  • Изображения: img[data-src-zoom]
  • Варианты: [data-variation-id]

Страницы магазинов

URL магазина: https://www.etsy.com/shop/ShopName. Etsy публично показывает бейдж «X sales» — это приблизительное число продаж с точностью ±10%. Количество листингов — через пагинацию: /shop/ShopName?section_id=0&page=2. Отзывы: /shop/ShopName/reviews.

Дерево категорий

Etsy организует товары в иерархию: https://www.etsy.com/c/clothing, /c/clothing/womens-clothing и так далее. Навигационное меню содержит ссылки на все верхние категории. Подкатегории извлекаются рекурсивно через ссылки на страницах категорий — селектор a[href*="/c/"]. Для POD-исследований категории — отправная точка: от них можно идти вниз до конкретных подкатегорий и оценивать конкуренцию.

Антибот Etsy: Cloudflare и лимиты запросов

Etsy использует двухуровневую защиту:

  1. Cloudflare WAF — JavaScript-челленджи и rate-limiting на уровне CDN. При подозрении Cloudflare показывает interstitial-страницу или 403. Residential-прокси значительно снижают вероятность челленджа, потому что IP выглядит как реальный пользователь из домашней сети.
  2. Внутренние лимиты Etsy — отслеживание частоты запросов с одного IP. Порог: примерно 100–200 запросов за 10–15 минут до появления 429 Too Many Requests. Лимиты строже для страниц поиска, чем для страниц отдельных листингов.

Практические правила:

  • Держите 1–2 запроса в секунду на один IP.
  • Ротируйте IP каждые 50–80 запросов.
  • Используйте residential-прокси — datacenter-IP Etsy блокирует агрессивнее.
  • Добавляйте реалистичные заголовки: User-Agent, Accept, Accept-Language, Referer.
  • Не пытайтесь программно обходить Cloudflare-челлендж — это эскалирует блокировку до банлиста.

Выбор типа прокси для Etsy

Тип проксиОбход CloudflareСкоростьРекомендация для Etsy
ResidentialОтличноСредняя (1–3 с)Лучший выбор — IP реальных пользователей
MobileОтличноНиже (2–5 с)Хорошо для sticky-сессий
DatacenterПлохоВысокая (<0.5 с)Только для невысоких объёмов

Для нишевых исследований на Etsy мы рекомендуем residential-прокси с ротацией по запросам. Настройка ProxyHat:

# HTTP residential proxy — ротация IP по каждому запросу
http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080

# Sticky-сессия для многостраничного скрапинга магазина
http://user-session-shop1:YOUR_PASSWORD@gate.proxyhat.com:8080

# Городское таргетирование — IP из конкретного города
http://user-country-US-city-new_york:YOUR_PASSWORD@gate.proxyhat.com:8080

Подробнее о доступных локациях — на странице ProxyHat Locations.

Паттерны скрапинга для поиска ниш

Трендовые поисковые запросы

Etsy не показывает трендовые запросы напрямую, но подстрока автодополнения доступна по адресу:

# Пример: автодополнения для запроса "mug"
curl -x http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080 \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \
  -H "Accept: application/json" \
  "https://www.etsy.com/api/v3/ajax/member/search-suggestions?query=mug"

Ответ — JSON с массивом предложений. Пример усечённого ответа:

{
  "results": [
    {"query": "mug", "display_name": "mug"},
    {"query": "mug rug", "display_name": "mug rug"},
    {"query": "mug holder", "display_name": "mug holder"},
    {"query": "mug cozy", "display_name": "mug cozy"},
    {"query": "mug tree", "display_name": "mug tree"},
    {"query": "mug personalized", "display_name": "mug personalized"}
  ]
}

Собирая подсказки для сотен seed-запросов, вы получаете карту спроса в категории. Для POD это особенно ценно: подсказки вроде «mug cozy» или «mug rug» указывают на субниши с меньшей конкуренцией.

Количество продавцов в нише

На странице поиска Etsy показывает «X,XXX results» — это общее число товаров, а не продавцов. Но каждый товар привязан к магазину. Собирая уникальные shop_id со страниц поиска, вы получаете оценку конкуренции. Алгоритм: скрапить 5–10 страниц результатов → извлечь data-shop-id из каждой карточки → посчитать уникальные значения.

Средние цены и ценовые сегменты

Извлекайте цены из карточек поиска и вычисляйте медиану, 25-й и 75-й перцентили. Это показывает, что покупатели готовы платить в данной нише — критически важно для POD-ценообразования. Если медианная цена кружки — $16, а вы планируете продавать за $30, это сигнал к пересмотру дизайна или позиционирования.

Полный цикл нишевого анализа

  1. Seed-запросы: соберите 50–100 ключевых слов через автодополнение.
  2. Объём результатов: для каждого запроса получите «X results» со страницы поиска.
  3. Конкуренция: извлеките уникальные shop_id — сколько продавцов в нише.
  4. Цены: соберите цены, вычислите статистику.
  5. Анализ лидеров: топ-10 магазинов по продажам — что они продают, как оформляют.
  6. Решение: ниша с <500 продавцов, медианная цена >$15 и растущим числом результатов — перспективна для POD.

Скрапинг поиска Etsy на Python

Ниже — полный пример: запрос страницы поиска → парсинг карточек → сбор данных для нишевого анализа.

import requests
from bs4 import BeautifulSoup
import re
import time
import random
import json

PROXY_USER = "user-country-US"
PROXY_PASS = "YOUR_PASSWORD"
PROXY_URL = f"http://{PROXY_USER}:{PROXY_PASS}@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": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Referer": "https://www.etsy.com/",
}

def fetch_search(query, page=1):
    """Получить страницу поиска Etsy."""
    url = f"https://www.etsy.com/search?q={query}&page={page}"
    resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=15)
    resp.raise_for_status()
    return resp.text

def parse_search_results(html):
    """Извлечь данные из карточек товаров."""
    soup = BeautifulSoup(html, "html.parser")
    listings = []

    cards = soup.select("div[data-results] .v2-listing-card")
    if not cards:
        cards = soup.select("[data-listing-id]")

    for card in cards:
        link = card.select_one("a.listing-link")
        title_el = card.select_one("h3")
        price_el = card.select_one(".currency-value")
        shop_el = card.select_one("[data-shop-id]")

        listing = {
            "title": title_el.get_text(strip=True) if title_el else None,
            "url": link["href"] if link and link.get("href") else None,
            "price": price_el.get_text(strip=True) if price_el else None,
            "listing_id": card.get("data-listing-id"),
            "shop_id": shop_el.get("data-shop-id") if shop_el else None,
        }
        if listing["url"]:
            listings.append(listing)

    # Общее число результатов
    results_text = soup.select_one(".wt-text-gray")
    total = None
    if results_text:
        match = re.search(r"([\d,]+)\s+results", results_text.get_text())
        if match:
            total = int(match.group(1).replace(",", ""))

    return {"listings": listings, "total_results": total}

# Собираем данные по нише
niche_data = []
for page in range(1, 6):
    html = fetch_search("custom+mug", page=page)
    result = parse_search_results(html)
    niche_data.extend(result["listings"])
    print(f"Page {page}: {len(result['listings'])} listings, "
          f"total: {result['total_results']}")
    time.sleep(random.uniform(2.0, 4.0))

Детальные страницы товаров с ротацией прокси

При переходе к страницам товаров важно ротировать IP, чтобы не упереться в лимиты. ProxyHat позволяет управлять ротацией через параметр session в имени пользователя — каждый новый session_id = новый IP.

def fetch_listing_detail(listing_url, session_id):
    """Получить детальную страницу товара с ротацией прокси."""
    proxy = f"http://user-session-{session_id}:{PROXY_PASS}@gate.proxyhat.com:8080"
    proxies = {"http": proxy, "https": proxy}

    resp = requests.get(listing_url, headers=HEADERS, proxies=proxies, timeout=15)
    resp.raise_for_status()
    return resp.text

def extract_listing_data(html):
    """Извлечь структурированные данные из ld+json блока."""
    soup = BeautifulSoup(html, "html.parser")
    data = {}

    for script in soup.find_all("script", type="application/ld+json"):
        try:
            ld = json.loads(script.string)
            if ld.get("@type") == "Product":
                data["name"] = ld.get("name")
                offers = ld.get("offers", {})
                data["price"] = offers.get("price")
                data["currency"] = offers.get("priceCurrency")
                data["description"] = (ld.get("description") or "")[:500]
        except (json.JSONDecodeError, TypeError):
            continue

    # Бейдж продаж продавца
    sales_el = soup.select_one("[data-sales-count]")
    if not sales_el:
        # Fallback: поиск по тексту
        for el in soup.select(".wt-text-gray"):
            match = re.search(r"(\d[\d,]*)\s+sales?", el.get_text())
            if match:
                data["seller_sales"] = int(match.group(1).replace(",", ""))
                break

    # ID и имя магазина
    shop_link = soup.select_one("a[data-shop-id]")
    if shop_link:
        data["shop_id"] = shop_link.get("data-shop-id")
        data["shop_name"] = shop_link.get_text(strip=True)

    return data

# Собираем детальные данные по листингам
all_details = []
for i, listing in enumerate(niche_data[:25]):
    session = f"etsy{i:04d}"
    try:
        detail_html = fetch_listing_detail(listing["url"], session)
        detail = extract_listing_data(detail_html)
        detail["search_rank"] = i + 1
        all_details.append(detail)
        print(f"{i+1}. {detail.get('name', 'N/A')[:60]} — "
              f"{detail.get('price', '?')} {detail.get('currency', '')}")
    except requests.HTTPError as e:
        print(f"{i+1}. HTTP error: {e.response.status_code}")
    time.sleep(random.uniform(1.5, 3.0))

Аналитика магазинов Etsy

Страница магазина — ключ к пониманию конкуренции. Etsy публично раскрывает:

  • Бейдж «X sales» — примерное количество продаж. Для нишевых исследований этого достаточно: сравнивайте продавцов между собой.
  • Количество листингов — доступно через пагинацию или атрибут data-listings-count.
  • Рейтинг и отзывы/shop/ShopName/reviews. Рейтинг и тексты отзывов дают понимание, что ценят покупатели.
def analyze_shop(shop_name):
    """Собрать базовую аналитику магазина Etsy."""
    url = f"https://www.etsy.com/shop/{shop_name}"
    proxy = f"http://user-country-US:{PROXY_PASS}@gate.proxyhat.com:8080"
    proxies = {"http": proxy, "https": proxy}

    resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=15)
    soup = BeautifulSoup(resp.text, "html.parser")
    shop_data = {"name": shop_name}

    # Количество продаж — бейдж «X sales»
    for el in soup.select(".wt-text-gray"):
        match = re.search(r"(\d[\d,]*)\s+sales?", el.get_text())
        if match:
            shop_data["sales_count"] = int(match.group(1).replace(",", ""))
            break

    # Количество листингов
    listing_count_el = soup.select_one("[data-listings-count]")
    if listing_count_el:
        shop_data["listing_count"] = int(
            listing_count_el["data-listings-count"]
        )

    # Рейтинг магазина
    rating_el = soup.select_one("[data-rating]")
    if rating_el:
        shop_data["rating"] = float(rating_el["data-rating"])

    return shop_data

# Анализируем уникальные магазины из ниши
unique_shops = set()
for detail in all_details:
    if detail.get("shop_name"):
        unique_shops.add(detail["shop_name"])

shop_analytics = []
for shop in list(unique_shops)[:10]:
    data = analyze_shop(shop)
    shop_analytics.append(data)
    print(f"{shop}: {data.get('sales_count', '?')} sales, "
          f"{data.get('listing_count', '?')} listings, "
          f"rating: {data.get('rating', '?')}")
    time.sleep(random.uniform(2.0, 4.0))

# Сводная статистика по нише
import statistics
prices = [float(d["price"]) for d in all_details if d.get("price")]
sales = [d["seller_sales"] for d in all_details if d.get("seller_sales")]
print(f"\nНишевая статистика:")
print(f"  Медианная цена: ${statistics.median(prices):.2f}")
print(f"  25-й перцентиль: ${statistics.quantiles(prices, n=4)[0]:.2f}")
print(f"  75-й перцентиль: ${statistics.quantiles(prices, n=4)[2]:.2f}")
print(f"  Средние продажи продавца: {statistics.mean(sales):.0f}")
print(f"  Уникальных магазинов: {len(unique_shops)}")

Этика: продавцы Etsy — малый бизнес

Etsy — площадка независимых продавцов. Многие из них — мастера, создающие уникальные товары ручной работы. При скрапинге соблюдайте принципы:

  • Исследуйте, не копируйте. Скрапинг для анализа трендов и цен — нормально. Копирование дизайнов, фотографий и описаний — нарушение авторских прав и прямой вред малому бизнесу.
  • Соблюдайте robots.txt. Etsy разрешает скрапинг определённых путей — проверяйте https://www.etsy.com/robots.txt перед запуском. Если путь запрещён — не скрапите его.
  • Не перегружайте серверы. Держите скорость ≤2 запросов/сек с ротацией IP. Агрессивный скрапинг замедляет сайт для реальных покупателей и продавцов.
  • Уважайте авторские права. Дизайны, фотографии и тексты защищены. Используйте данные для исследований рынка, а не для воспроизведения чужого труда.
  • GDPR/CCPA. Не собирайте персональные данные продавцов сверх необходимого для аналитики. Имя магазина и количество продаж — достаточно.

Скрапинг Etsy для нишевых исследований — инструмент, а не оружие. Цель — понять рынок и найти свою нишу, а не обмануть продавцов.

Подробнее об этике и лучших практиках скрапинга — в нашем материале о веб-скрапинге и этике.

Ключевые выводы

  • Etsy Open API не подходит для нишевых исследований — скрапинг HTML остаётся основным подходом.
  • Residential-прокси обязательны: Cloudflare и внутренние лимиты Etsy быстро блокируют datacenter-IP.
  • Держите 1–2 запроса/сек, ротируйте IP каждые 50–80 запросов через ProxyHat.
  • Структурированные данные в ld+json — самый надёжный источник на страницах товаров.
  • Бейдж «X sales» и количество листингов — ключевые метрики для оценки конкуренции в нише.
  • API автодополнений — быстрый способ собрать seed-запросы для нишевого анализа.
  • Скрапите для исследований, не для копирования. Уважайте труд независимых продавцов.

Готовы начать?

Доступ к более чем 50 млн резидентных IP в 148+ странах с AI-фильтрацией.

Смотреть ценыРезидентные прокси
← Вернуться в Блог