Эта статья была полезной?
Как парсить SHEIN: Практическое руководство
Технический специалист
SHEIN — крупная e-commerce платформа в сфере моды. Парсинг её страниц поиска, категорий и карточек товаров сложнее обычной загрузки HTML и поиска элементов по CSS-селекторам.
Основная сложность заключается в двух факторах:
- Данные о товарах динамически внедряются в JavaScript-состояние страницы (window.gbRawData для листингов) или в schema.org JSON-LD (для карточек товаров), а не рендерятся в простом HTML.
- SHEIN использует проприетарную систему оценки рисков, которая может перенаправлять автоматизированные сессии на /risk/challenge (интерактивная проверка) или /risk/action/limit (жёсткое ограничение частоты запросов).
В этом руководстве мы разберём, как работает open-source проект 2scraper/shein-scraper. Мы сосредоточимся на движке Playwright, так как он является рекомендуемым и наиболее стабильным в этом репозитории.
Что мы создадим
Скрапер, способный работать в трёх режимах:
- Поиск товаров по ключевому слову.
- Парсинг конкретной категории SHEIN.
- Парсинг отдельной страницы товара.
Извлекаемые данные включают: sku, title, brand, price, original_price, currency, discount_pct, rating, review_count, in_stock, is_clearance, quickship и product_url.
Важно: Текущая реализация ориентирована на us.shein.com. Не следует автоматически предполагать, что другие региональные домены используют идентичные структуры данных или эндпоинты.
Требования
- Python: 3.9 или новее.
- Браузерный движок: Playwright (рекомендуется). Зависимости для Selenium и Puppeteer в проекте разделены, так как их пакеты могут конфликтовать.
- Внешние сервисы (опционально):
- Ключ API 2Captcha (TWOCAPTCHA_KEY), если требуется автоматическое решение визуальных задач challenge.
- CDP-эндпоинт Scraping Browser (SHEIN_CDP_ENDPOINT) для использования постоянных браузерных профилей.
Настройка проекта
-
Клонируйте репозиторий и настройте окружение:
bashgit clone https://github.com/2scraper/shein-scraper.git cd shein-scraper python3 -m venv .venv source .venv/bin/activate # Для Windows: .venv\Scripts\activate -
Установите зависимости и браузер:
bashpip install -r requirements-playwright.txt playwright install chromium -
Настройте переменные окружения:
bashcp .env.example .envОтредактируйте .env, добавив при необходимости TWOCAPTCHA_KEY, SHEIN_PROXY или SHEIN_CDP_ENDPOINT. Хранение учётных данных в .env предпочтительнее передачи их через аргументы командной строки.
Быстрый старт
Самый простой способ запустить скрапер — через CLI:
bash
# Поиск по ключевому слову
python3 playwright_scraper.py --query "summer dress" --max-results 20
# Парсинг категории (путь берётся из реального URL SHEIN, например: Women-Jeans-c-1934.html)
python3 playwright_scraper.py --category "Women-Jeans-c-1934.html" --format csv --out jeans.csv
# Парсинг отдельной карточки товара
python3 playwright_scraper.py --url "https://us.shein.com/dsbayvkj-p-33704388.html"
По умолчанию результаты сохраняются в shein_results.json. Рядом создаётся файл метаданных shein_results.json.meta.json, который критически важен для автоматизации: он позволяет отличить ситуацию «товаров действительно нет» от «скрапер был заблокирован».
Как работает скрапер
Архитектура репозитория разделяет браузерный движок и логику парсинга. Файлы playwright_scraper.py, selenium_scraper.py и puppeteer_scraper.py используют общую логику выполнения из page_flow.py.
Упрощённый поток выполнения:
- Формирование URL (на основе фактической структуры SHEIN, а не выдуманных внутренних API).
- Запуск браузера и переход на страницу.
- Мгновенная проверка URL: если адрес содержит /risk/challenge или /risk/action/limit, сессия помечается как заблокированная.
- Ожидание и прокрутка страницы (для листингов).
- Извлечение window.gbRawData (предпочтительно через page.evaluate, с фоллбэком на парсинг HTML).
- Парсинг, дедупликация и сохранение результатов.
Почему используется браузер, а не requests?
Потому что основной источник данных — это JavaScript-объект. Браузерный адаптер может получить его напрямую (() => window.gbRawData), что намного надёжнее, чем попытка распарсить сложный, глубоко вложенный JSON из сырого HTML с помощью регулярных выражений.
Извлечение данных
1. Поиск и категории (Основной путь)
Данные извлекаются из window.gbRawData. Скрапер читает исходные записи товаров, а не текст со страницы:
python
goods_id = raw.get("goods_id")
price = _money(raw.get("salePrice"))
original_price = _money(raw.get("retailPrice"))
Скрапер также извлекает заявленное общее количество результатов (sum или result_count). Это позволяет программе понять, был ли сбор намеренно остановлен параметром --max-results или скрапер неожиданно получил меньше товаров, чем сообщил сайт.
2. Отдельная страница товара
Для карточек товаров используется другой путь. Скрапер ищет <script type="application/ld+json"> и парсит schema.org ProductGroup или Product.
Важно: Репозиторий не использует JSON-LD для страниц поиска, так как там он содержит только данные навигации (breadcrumb), а не список товаров.
3. DOM-парсинг (Fallback)
В коде присутствуют резервные CSS-селекторы (например, .product-card, [data-goods-id]). Однако в исходном коде они явно помечены комментарием # TODO: verify live. Это означает, что они являются best-effort решением на случай, если window.gbRawData исчезнет, и не должны считаться подтверждённым основным методом извлечения.
Работа с Anti-Bot защитой SHEIN
Репозиторий чётко разделяет два типа защитных механизмов, и скрапер реагирует на них по-разному.
/risk/challenge (Интерактивная проверка)
Это шлюз проверки сессии. Реализация в репозитории распознаёт специфичные для SHEIN визуальные задачи, а не пытается выдать их за стандартную reCAPTCHA:
- CAPTCHA с сеткой 3×3: Скрапер делает скриншот, кодирует его в Base64 и отправляет как GridTask. В ответ он получает номера ячеек (например, [1, 4, 7]), преобразует их в координаты и кликает по ним.
- CAPTCHA с последовательностью иконок: Отправляется как CoordinatesTask. Ответ содержит массив координат {"x": 120, "y": 83}, которые скрапер масштабирует под CSS-размеры окна браузера (важно для дисплеев с высоким DPI) и эмулирует клики.
После кликов скрапер проверяет, исчезла ли страница challenge. Если нет, задача может быть обновлена и решена повторно (до лимита --risk-challenge-rounds).
/risk/action/limit (Rate Limit)
Это не CAPTCHA. Это жёсткое ограничение частоты запросов. Отправлять такую страницу в сервис решения CAPTCHA бессмысленно. Скрапер распознаёт этот путь, логирует его и может подождать заданное время (--rate-limit-cooldown 300), прежде чем завершить работу или повторить попытку.
Работа с CAPTCHA (Интеграция с 2Captcha)
Если включён ключ TWOCAPTCHA_KEY, скрапер автоматически делегирует решение визуальных задач. Жизненный цикл запроса для GridTask выглядит так:
python
import base64
import requests
API_KEY = "YOUR_2CAPTCHA_KEY"
with open("challenge_grid.png", "rb") as f:
body = base64.b64encode(f.read()).decode()
# 1. Создание задачи
response = requests.post(
"https://api.2captcha.com/createTask",
json={
"clientKey": API_KEY,
"task": {
"type": "GridTask",
"body": body,
"rows": 3,
"columns": 3,
"comment": "Select the images matching the instruction"
}
}
)
task_id = response.json()["taskId"]
# 2. Ожидание результата
while True:
result = requests.post(
"https://api.2captcha.com/getTaskResult",
json={"clientKey": API_KEY, "taskId": task_id}
).json()
if result.get("status") == "ready":
clicks = result["solution"]["click"] # Например: [1, 4, 7]
break
time.sleep(5)
# 3. Преобразование clicks в координаты и эмуляция кликов в браузере
Важно: Получение правильного ответа от API решения CAPTCHA не гарантирует, что SHEIN примет сессию. Если риск-скоринг остаётся высоким, challenge может появиться снова. Параметр --max-solves защищает от бесконечных списаний средств.
Использование прокси
Поддержка прокси встроена в репозиторий и не является просто теоретической рекомендацией. Поддерживается формат http://user:pass@host:port или компактный host:port:login:password.
bash
python3 playwright_scraper.py --query "jeans" --proxy-file proxies.txt
Внутренний proxy_pool.py использует round-robin выборку и отслеживает повторяющиеся ошибки, временно исключая нерабочие прокси. В Playwright учётные данные передаются через штатную конфигурацию, что надёжнее, чем внедрение их в аргументы запуска Chromium.
Использование 2Captcha Browser API (CDP)
Playwright-реализация поддерживает подключение к удалённому браузеру через Chrome DevTools Protocol (CDP) вместо запуска локального Chromium:
bash
python3 playwright_scraper.py --query "summer dress" --cdp-endpoint "$SHEIN_CDP_ENDPOINT"
Когда это предпочтительнее:
- Локальные сессии постоянно попадают на /risk/challenge.
- Необходимо сохранять cookies и состояние браузера между запусками (постоянный профиль).
- Требуется отделить инфраструктуру браузеров от логики скрапера.
Удалённый браузер сам по себе не даёт 100% гарантии доступа, но он радикально снижает частоту появления risk gateway по сравнению с созданием нового "чистого" профиля при каждом запуске.
Масштабирование скрапера
Репозиторий предоставляет базовые механизмы (retry, jitter, proxy rotation), но для production-нагрузок требуется оркестрация:
- Ограничивайте параллельность: Не запускайте сотни браузеров одновременно. Начинайте с малого пула и мониторьте частоту появления challenge. Репозиторий не содержит подтверждённых "безопасных" лимитов запросов для SHEIN.
- Сохраняйте сессии: Для периодического мониторинга повторное использование одного CDP-профиля эффективнее постоянной генерации новых.
- Добавляйте jitter: Параметры --delay-jitter и --scroll-delay предотвращают синхронные всплески запросов от множества воркеров.
- Проверяйте полноту: Сравнивайте количество собранных товаров с полем result_count из window.gbRawData. Если скрапер собрал 20 товаров, а сайт сообщает о 1500, запуск следует считать частичным (особенно учитывая, что загрузка дополнительных данных при скроллинге помечена в репо как требующая дополнительной live-проверки).
Устранение проблем
| Проблема | Возможная причина | Решение |
|---|---|---|
| Перенаправление на /risk/challenge | SHEIN запросил проверку сессии | Используйте встроенный challenge handler. Если профиль постоянно отклоняется, смените CDP-эндпоинт или прокси. |
| Открытие /risk/action/limit | Сработал rate limit | Не отправляйте это в решатель CAPTCHA. Уменьшите частоту запросов, используйте --rate-limit-cooldown или повторите позже. |
| Возвращается 0 товаров | Выдача пуста, или данные не загрузились | Проверьте .meta.json файл. Используйте --dump-html, чтобы увидеть, не отдал ли сайт страницу согласия или ошибку. |
| Собирается только первая партия товаров (~20 шт.) | Scroll-driven загрузка больших каталогов не полностью подтверждена в текущей версии | Считайте большие выборки потенциально частичными. Для полного парсинга категории используйте пагинацию по реальным URL подкатегорий. |
| CAPTCHA решена, но challenge остался | SHEIN отклонил сессию несмотря на правильное решение визуальной задачи | Получите новую задачу (в пределах --max-solves) или смените профиль/прокси. |
| DOM fallback перестал работать | Селекторы помечены как TODO: verify live | Не полагайтесь на них. Основной источник — window.gbRawData. |
Пример результата
Поле price_source в выводе указывает на происхождение данных, что критически важно для аудита:
json
{
"sku": "458057728",
"source": "shein.com",
"category": "Women Mini Dresses",
"title": "Aloruh Women's Solid Color Sleeveless Mini Dress",
"brand": "Aloruh",
"price": 13.03,
"currency": "USD",
"price_source": "embedded_json",
"product_url": "https://us.shein.com/Aloruh-Women-s-Solid-Color-Sleeveless-Mini-Dress-p-458057728.html",
"image_url": "https://img.ltwebstatic.com/v4/j/pi/.../thumbnail_405x552.jpg",
"scraped_at": "2026-09-30T12:56:49Z",
"original_price": 20.89,
"discount_pct": 38.0,
"rating": 4.62,
"review_count": 1001,
"in_stock": true,
"is_clearance": false,
"quickship": false
}
Примечание: Для страниц отдельных товаров price_source может принимать значения json_ld или json_ld_min_variant (если цена взята как минимальная среди вариантов).
Полный компактный пример кода
Ниже приведён минимальный рабочий пример, демонстрирующий ключевую технику репозитория: получение window.gbRawData через Playwright с базовой обработкой ошибок.
python
import asyncio
from urllib.parse import quote
from playwright.async_api import async_playwright
BASE_URL = "https://us.shein.com"
def search_url(query: str) -> str:
return f"{BASE_URL}/pdsearch/{quote(query)}/"
def parse_products(data: dict, limit: int = 20) -> list[dict]:
results = data.get("results") or {}
info = results.get("bffProductsInfo") or {}
raw_products = info.get("products") or []
products = []
for raw in raw_products[:limit]:
goods_id = raw.get("goods_id")
if not goods_id:
continue
products.append({
"sku": str(goods_id),
"title": raw.get("goods_name"),
"price": raw.get("salePrice", {}).get("amount"),
"product_url": f"{BASE_URL}/{raw.get('goods_url_name') or 'product'}-p-{goods_id}.html",
})
return products
async def main():
async with async_playwright() as p:
# В production здесь можно добавить proxy={"server": "..."}
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()
url = search_url("summer dress")
print(f"Переход по URL: {url}")
await page.goto(url, wait_until="domcontentloaded", timeout=60_000)
# Проверка на риск-шлюзы
if "/risk/challenge" in page.url or "/risk/action/limit" in page.url:
print(f"Сессия заблокирована или ограничена: {page.url}")
await browser.close()
return
# Предпочтительный метод извлечения
data = await page.evaluate("() => window.gbRawData || null")
if not data:
print("window.gbRawData не найден. Возможна anti-bot страница или изменение фронтенда.")
await browser.close()
return
products = parse_products(data, limit=5)
print(f"Успешно извлечено {len(products)} товаров:")
for p in products:
print(f" - {p['sku']}: {p['title']} ({p['price']} USD)")
await browser.close()
if __name__ == "__main__":
asyncio.run(main())
Этот пример намеренно не включает обработку challenge, ротацию прокси и скроллинг, чтобы продемонстрировать именно ядро извлечения данных.
Тестирование и ограничения
В репозитории присутствуют offline smoke-тесты (python3 smoke_test.py), которые проверяют детерминированную логику парсинга и построения задач CAPTCHA.
Однако важно чётко разделять offline-тесты и live-поведение сайта. Согласно документации проекта, на момент последних проверок подтверждены:
- Работа Playwright с window.gbRawData.
- Использование JSON-LD для карточек товаров.
- Распознавание путей /risk/challenge и /risk/action/limit.
Области, требующие дополнительной live-проверки (помечены в коде как TODO: verify live):
- Загрузка дополнительных результатов при скроллинге beyond первой партии.
- Работоспособность DOM fallback селекторов.
- End-to-end сценарии для Selenium и Puppeteer.
Не следует превращать эти неподтверждённые возможности в гарантированные утверждения при построении production-пайплайна.
Заключение
Надёжный парсинг SHEIN — это не поиск "волшебных" CSS-селекторов, а понимание того, откуда фронтенд получает структурированные данные. Использование window.gbRawData для листингов и JSON-LD для карточек товаров делает скрапер устойчивым к косметическим изменениям вёрстки.
Anti-bot состояния должны рассматриваться как часть стандартного workflow: /risk/challenge обрабатывается через визуальные задачи (Grid/Coordinates), а /risk/action/limit требует стратегического ожидания, а не попыток решения CAPTCHA.
Для разовых задач достаточно локального Playwright. Для регулярного мониторинга цен настоятельно рекомендуется использовать постоянные браузерные сессии (через CDP), прокси и встроенный инструмент diff_runs.py для сравнения результатов, что позволяет избежать ложных выводов о "пропаже" товаров из-за временной блокировки скрапера.