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 API | Browse API | HTML-скрейпинг |
|---|---|---|---|
| Лимит вызовов | 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.






