Как скрейпить eBay: API, HTML-структуры и прокси для реселлеров

Практическое руководство по скрейпингу eBay — от выбора между Finding API и HTML-парсингом до работы с .s-item, аукционами и аналитикой продавцов через резидентные прокси.

Как скрейпить eBay: API, HTML-структуры и прокси для реселлеров

API eBay vs HTML-скрейпинг: что выбрать

У eBay есть два официальных API для поиска товаров: Finding API и Browse API. Оба работают, пока вы не упираетесь в лимиты. Finding API возвращает до 100 страниц по 100 товаров на запрос (10 000 записей максимум), а Browse API ограничивает 10 000 вызовов в день на приложение. Для реселлер-аналитики, где нужно сканировать десятки категорий по нескольким регионам, этого мало.

Кроме квот, есть и качественные отличия: API не отдают всё, что видно на странице — например, динамические данные аукционов (оставшееся время, текущая ставка), HTML-баннеры продавцов, вариации товаров и региональные цены. Вот тут и вступает скрейпинг HTML.

Практическое правило: если вам нужен каталог до 10 000 товаров в день по одному рынку — начинайте с API. Если вы строите мониторинг цен, отслеживаете аукционы или покрываете несколько доменов eBay (eBay.de, eBay.co.uk, eBay.com.au) — скрейпинг через проки экономит недели разработки.

КритерийFinding APIBrowse APIHTML-скрейпинг
Лимит вызовов5 000 / день10 000 / деньОпределяется прокси-пулом
Макс. результатов на запрос10 000Пагинация через offsetБез ограничений
Данные аукционовТолько ценаЧастичноПолные (таймер, ставки)
Региональные ценыТребует отдельных вызововПо siteidЕстественно по домену
Анти-бот защитаНетНетeBay блокирует DC-IP
Сложность настройкиСредняя (OAuth)Средняя (OAuth)Низкая (requests + парсер)

Целевые HTML-структуры eBay

Страница результатов поиска — сетка .s-item

Поисковая выдача eBay рендерит карточки товаров внутри контейнера .srp-river-main. Каждый товар — элемент с классом .s-item. Внутри него:

  • .s-item__title — заголовок товара;
  • .s-item__price — текущая цена;
  • .s-item__shipping — стоимость доставки;
  • .s-item__bid-count — количество ставок (для аукционов);
  • .s-item__time-left — оставшееся время аукциона;
  • .s-item__purchase-options — флаг Buy It Now;
  • .s-item__seller-info — имя продавца;
  • .s-item__link — URL карточки товара.

Классы стабильны, но eBay периодически добавляет A/B-варианты с суффиксами --variant. Надёжнее искать по базовому классу через [class^="s-item__"] или contains в XPath.

Страница товара (listing detail)

URL вида /itm/123456789. Ключевые селекторы:

  • #vi-tab-details — вкладка характеристик;
  • .ux-layout-section--itemDetails — блок цены и вариантов;
  • #bidBtn_btn — кнопка ставки (наличие = аукцион);
  • #binBtn_btn — кнопка Buy It Now;
  • .d-feedback-summary — оценка продавца.

Профиль продавца

Страница /usr/username или /str/store_name. Содержит:

  • .seller-info__feedback-score — количество отзывов;
  • .seller-info__positive-feedback — процент позитивных;
  • .seller-info__member-since — дата регистрации;
  • .str-store-categories — категории магазина.

Почему eBay агрессивно блокирует дата-центровые IP

eBay использует многоуровневую защиту: fingerprinting браузера, rate limiting по IP-подсетям и эвристику по ASN. Дата-центровые подсети (AWS, DigitalOcean, OVH) попадают в блэклисты автоматически. Один запрос с DC-IP может пройти, но на пятом-десятом вы получите 403 или редирект на /signin.

Решение — резидентные прокси. Они используют IP реальных пользователей, и eBay не отличает ваш скрейпер от обычного покупателя. Для массового скрейпинга нужен пул с ротацией: каждый запрос — новый IP.

Для региональных доменов (eBay.de, eBay.co.uk) критична гео-таргетировка. eBay показывает разные цены и разные товары в зависимости от страны покупателя. Если вы скрейпите eBay.de с американского IP, вы увидите «international version» listings вместо локальных.

Резидентные прокси с гео-таргетировкой — не опция, а необходимость для скрейпинга eBay. Дата-центровые IP не пройдут даже базовую проверку.

Настройка прокси ProxyHat для скрейпинга eBay

Простейший способ — передать учётные данные прокси в URL. Для HTTP-запросов используем порт 8080, для SOCKS5 — 1080.

Базовый формат:

# HTTP — ротация IP на каждый запрос
curl -x http://user-country-US:PASSWORD@gate.proxyhat.com:8080 \
  "https://www.ebay.com/sch/i.html?_nkw=rtx+4090&_pgn=1"

# SOCKS5 — для более стойких сессий
curl -x socks5://user-country-DE:PASSWORD@gate.proxyhat.com:1080 \
  "https://www.ebay.de/sch/i.html?_nkw=rtx+4090&_pgn=1"

Для sticky-сессий (когда нужно пройти несколько страниц от одного «пользователя») добавьте флаг сессии в username:

# Sticky-сессия — один IP на 10+ минут
curl -x http://user-country-US-session-mysess1:PASSWORD@gate.proxyhat.com:8080 \
  "https://www.ebay.com/sch/i.html?_nkw=rtx+4090&_pgn=1"

Гео-таргетировка до города:

# Лондонский IP для eBay.co.uk
curl -x http://user-country-GB-city-london:PASSWORD@gate.proxyhat.com:8080 \
  "https://www.ebay.co.uk/sch/i.html?_nkw=iphone+15&_pgn=1"

Python: скрейпер результатов поиска eBay

Ниже — рабочий пример, который забирает страницы поиска и парсит .s-item в структурированные записи. Используем requests + BeautifulSoup + ротацию прокси ProxyHat.

import re
import csv
import time
import requests
from bs4 import BeautifulSoup

PROXY_USER = "user-country-US"
PROXY_PASS = "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-Language": "en-US,en;q=0.9",
}

def parse_price(text):
    """Извлекает числовое значение цены из строки."""
    m = re.search(r"[\d,]+\.\d{2}", text.replace(",", ""))
    return float(m.group()) if m else None

def scrape_search(keyword, max_pages=5):
    records = []
    for page in range(1, max_pages + 1):
        url = (
            f"https://www.ebay.com/sch/i.html"
            f"?_nkw={keyword}&_pgn={page}&_ipg=240"
        )
        resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=30)
        if resp.status_code != 200:
            print(f"Page {page}: HTTP {resp.status_code} — stopping")
            break

        soup = BeautifulSoup(resp.text, "html.parser")
        items = soup.select(".s-item")
        if not items:
            print(f"Page {page}: no items found — stopping")
            break

        for item in items:
            title_el = item.select_one(".s-item__title")
            if not title_el:
                continue
            title = title_el.get_text(strip=True)
            # Пропускаем рекламные вставки
            if title.lower().startswith("shop on ebay"):
                continue

            price_el = item.select_one(".s-item__price")
            price = parse_price(price_el.get_text()) if price_el else None

            shipping_el = item.select_one(".s-item__shipping")
            shipping = shipping_el.get_text(strip=True) if shipping_el else None

            link_el = item.select_one(".s-item__link")
            link = link_el["href"] if link_el else None

            # Аукционные поля
            bid_el = item.select_one(".s-item__bid-count")
            bids = bid_el.get_text(strip=True) if bid_el else None

            time_el = item.select_one(".s-item__time-left")
            time_left = time_el.get_text(strip=True) if time_el else None

            bin_el = item.select_one(".s-item__purchase-options")
            is_bin = bin_el is not None

            seller_el = item.select_one(".s-item__seller-info")
            seller = seller_el.get_text(strip=True) if seller_el else None

            records.append({
                "title": title,
                "price": price,
                "shipping": shipping,
                "link": link,
                "bids": bids,
                "time_left": time_left,
                "buy_it_now": is_bin,
                "seller": seller,
            })

        print(f"Page {page}: {len(items)} items scraped")
        time.sleep(2)  # вежливая задержка между страницами

    return records

if __name__ == "__main__":
    data = scrape_search("rtx 4090", max_pages=3)
    with open("ebay_rtx4090.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=data[0].keys())
        writer.writeheader()
        writer.writerows(data)
    print(f"Saved {len(data)} records")

Параметр _ipg=240 в URL увеличивает количество товаров на страницу до максимума (240), что сокращает число запросов в 2.4 раза по сравнению с дефолтным 60. Это важно: меньше запросов — ниже риск блокировки.

Обработка аукционов: время, ставки, Buy It Now

Аукционы — самая ценная часть данных eBay для реселлеров. На странице поиска eBay показывает:

  • Текущую ставку — в .s-item__price, с пометкой «X bids»;
  • Количество ставок — в .s-item__bid-count (например, «12 bids»);
  • Оставшееся время — в .s-item__time-left (например, «2h 15m left»);
  • Buy It Now — наличие .s-item__purchase-options с текстом «Buy It Now».

Для детальных данных аукциона нужно зайти на страницу товара. Там eBay рендерит JavaScript-таймер обратного отсчёта. Его значение можно извлечь из атрибута data-ending-time или из JSON в script-теге с типом application/ld+json.

import json
import requests
from bs4 import BeautifulSoup
from datetime import datetime, timezone

def scrape_listing_detail(item_url, proxies):
    """Парсит детальную страницу товара eBay."""
    resp = requests.get(item_url, headers=HEADERS, proxies=proxies, timeout=30)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Извлекаем JSON-LD — структурированные данные товара
    ld_script = soup.find("script", type="application/ld+json")
    ld_data = json.loads(ld_script.string) if ld_script else {}

    # Тип продажи
    offers = ld_data.get("offers", {})
    offer_type = offers.get("@type", "")  # Offer, AggregateOffer
    price = offers.get("price") or offers.get("lowPrice")

    # Время окончания аукциона
    end_time_str = offers.get("priceValidUntil") or offers.get("availability")
    end_time = None
    if end_time_str:
        try:
            end_time = datetime.fromisoformat(
                end_time_str.replace("Z", "+00:00")
            )
        except (ValueError, AttributeError):
            pass

    # HTML-селекторы для аукциона
    bid_btn = soup.select_one("#bidBtn_btn")
    bin_btn = soup.select_one("#binBtn_btn")
    bid_count_el = soup.select_one("[data-testid='x-bid-count']")

    return {
        "url": item_url,
        "name": ld_data.get("name"),
        "price": price,
        "currency": offers.get("priceCurrency", "USD"),
        "is_auction": bid_btn is not None,
        "is_buy_it_now": bin_btn is not None,
        "bid_count": bid_count_el.get_text(strip=True) if bid_count_el else None,
        "end_time": end_time.isoformat() if end_time else None,
        "seller_name": ld_data.get("seller", {}).get("name"),
        "condition": ld_data.get("itemCondition", ""),
    }

# Пример вызова
result = scrape_listing_detail(
    "https://www.ebay.com/itm/1234567890",
    PROXIES
)
print(json.dumps(result, indent=2, ensure_ascii=False))

Пример усечённого ответа JSON-LD со страницы товара:

{
  "@type": "Product",
  "name": "NVIDIA RTX 4090 Founders Edition",
  "offers": {
    "@type": "Offer",
    "price": "1599.99",
    "priceCurrency": "USD",
    "priceValidUntil": "2025-02-15T20:30:00Z",
    "seller": { "name": "gpu_deals_us" }
  },
  "itemCondition": "https://schema.org/NewCondition"
}

Аналитика продавцов: отзывы, категории, кросс-листинг

Для реселлер-разведки недостаточно знать цену товара — нужно понимать, кто продаёт и как. Ключевые метрики продавца:

  • Feedback score — общее количество отзывов (в профиле и на странице товара);
  • Positive feedback % — доля позитивных (ниже 98% — красный флаг);
  • Member since — возраст аккаунта (новый аккаунт с 10 000 отзывов — подозрительно);
  • Store categories — что продаёт (если продаёт и кроссовки, и автозапчасти — вероятно, дропшиппер);
  • Listing velocity — сколько новых товаров в день (резкий всплеск — возможна распродажа или скам).

Скрейпинг профиля продавца через ProxyHat с гео-таргетировкой:

def scrape_seller_profile(seller_name, country="US"):
    """Парсит профиль продавца eBay."""
    proxy_user = f"user-country-{country}"
    proxy_url = f"http://{proxy_user}:{PROXY_PASS}@gate.proxyhat.com:8080"
    proxies = {"http": proxy_url, "https": proxy_url}

    url = f"https://www.ebay.com/usr/{seller_name}"
    resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Feedback score
    fb_el = soup.select_one(".seller-info__feedback-score")
    feedback_score = fb_el.get_text(strip=True) if fb_el else None

    # Positive %
    pos_el = soup.select_one(".seller-info__positive-feedback")
    positive_pct = pos_el.get_text(strip=True) if pos_el else None

    # Member since
    since_el = soup.select_one(".seller-info__member-since")
    member_since = since_el.get_text(strip=True) if since_el else None

    # Store categories
    cat_els = soup.select(".str-store-categories a")
    categories = [c.get_text(strip=True) for c in cat_els]

    return {
        "seller": seller_name,
        "feedback_score": feedback_score,
        "positive_pct": positive_pct,
        "member_since": member_since,
        "categories": categories,
        "category_count": len(categories),
    }

# Анализ продавца
profile = scrape_seller_profile("gpu_deals_us", country="US")
print(json.dumps(profile, indent=2, ensure_ascii=False))

Паттерн кросс-листинга: если продавец листит один и тот же товар на eBay.com, eBay.co.uk и eBay.de с разницей в цене 15–30%, это арбитражная стратегия. Обнаружить её можно, только скрейпив несколько региональных доменов с локальными IP — что без гео-таргетированных резидентных прокси невозможно.

Рекомендации по надёжности скрейпинга

  • Ротация IP на каждый запрос — дефолтный режим ProxyHat без флага -session-. Для eBay это основной режим.
  • Sticky-сессии — только если нужно пройти пагинацию или добавить товар в корзину от одного «пользователя». Таймаут — до 30 минут.
  • Задержка 1–3 секунды между запросами с одного потока. Параллелизм — через несколько потоков с разными прокси.
  • Ротация User-Agent — eBay проверяет UA на консистентность. Используйте fake-useragent или аналоги.
  • Обработка CAPTCHA — при частых запросах eBay может вернуть страницу с reCAPTCHA. Уменьшите скорость или расширьте пул прокси.
  • Соблюдайте robots.txt — eBay ограничивает скрейпинг определённых путей. Проверяйте /robots.txt перед запуском.

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

  • API eBay — для малых объёмов (до 10 000 товаров/день). Скрейпинг — для масштабного мониторинга и аукционных данных.
  • Селектор .s-item — основа парсинга поиска. Запомните дочерние классы: __title, __price, __bid-count, __time-left.
  • Только резидентные прокси — дата-центровые IP eBay блокирует на 5–10 запросе.
  • Гео-таргетировка обязательна для региональных доменов — eBay.de, eBay.co.uk, eBay.com.au показывают разные цены.
  • Аукционные данные (таймер, ставки, BIN) доступны только через HTML-скрейпинг.
  • Аналитика продавцов — ключ к реселлер-разведке: feedback score, категории, кросс-листинг.

Готовы начать? Настройте резидентные прокси ProxyHat за минуты и запускайте первый скрейп. Полный список локаций — на странице локаций. Больше сценариев использования — в разделе Web Scraping и SERP Tracking.

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

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

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