Логотип «2Captcha»Перейти на главную страницу
Туториалы по обходу капчи

Эта статья была полезной?

Как парсить SHEIN: Практическое руководство

Грегори Фишер
Грегори Фишер

Технический специалист

SHEIN — крупная e-commerce платформа в сфере моды. Парсинг её страниц поиска, категорий и карточек товаров сложнее обычной загрузки HTML и поиска элементов по CSS-селекторам.

Основная сложность заключается в двух факторах:

  1. Данные о товарах динамически внедряются в JavaScript-состояние страницы (window.gbRawData для листингов) или в schema.org JSON-LD (для карточек товаров), а не рендерятся в простом HTML.
  2. 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) для использования постоянных браузерных профилей.

Настройка проекта

  1. Клонируйте репозиторий и настройте окружение:

    bash Copy
    git clone https://github.com/2scraper/shein-scraper.git
    cd shein-scraper
    python3 -m venv .venv
    source .venv/bin/activate  # Для Windows: .venv\Scripts\activate
  2. Установите зависимости и браузер:

    bash Copy
    pip install -r requirements-playwright.txt
    playwright install chromium
  3. Настройте переменные окружения:

    bash Copy
    cp .env.example .env

    Отредактируйте .env, добавив при необходимости TWOCAPTCHA_KEY, SHEIN_PROXY или SHEIN_CDP_ENDPOINT. Хранение учётных данных в .env предпочтительнее передачи их через аргументы командной строки.


Быстрый старт

Самый простой способ запустить скрапер — через CLI:

bash Copy
# Поиск по ключевому слову
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.

Упрощённый поток выполнения:

  1. Формирование URL (на основе фактической структуры SHEIN, а не выдуманных внутренних API).
  2. Запуск браузера и переход на страницу.
  3. Мгновенная проверка URL: если адрес содержит /risk/challenge или /risk/action/limit, сессия помечается как заблокированная.
  4. Ожидание и прокрутка страницы (для листингов).
  5. Извлечение window.gbRawData (предпочтительно через page.evaluate, с фоллбэком на парсинг HTML).
  6. Парсинг, дедупликация и сохранение результатов.

Почему используется браузер, а не requests?
Потому что основной источник данных — это JavaScript-объект. Браузерный адаптер может получить его напрямую (() => window.gbRawData), что намного надёжнее, чем попытка распарсить сложный, глубоко вложенный JSON из сырого HTML с помощью регулярных выражений.


Извлечение данных

1. Поиск и категории (Основной путь)

Данные извлекаются из window.gbRawData. Скрапер читает исходные записи товаров, а не текст со страницы:

python Copy
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:

  1. CAPTCHA с сеткой 3×3: Скрапер делает скриншот, кодирует его в Base64 и отправляет как GridTask. В ответ он получает номера ячеек (например, [1, 4, 7]), преобразует их в координаты и кликает по ним.
  2. 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 Copy
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 Copy
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 Copy
python3 playwright_scraper.py --query "summer dress" --cdp-endpoint "$SHEIN_CDP_ENDPOINT"

Когда это предпочтительнее:

  • Локальные сессии постоянно попадают на /risk/challenge.
  • Необходимо сохранять cookies и состояние браузера между запусками (постоянный профиль).
  • Требуется отделить инфраструктуру браузеров от логики скрапера.

Удалённый браузер сам по себе не даёт 100% гарантии доступа, но он радикально снижает частоту появления risk gateway по сравнению с созданием нового "чистого" профиля при каждом запуске.


Масштабирование скрапера

Репозиторий предоставляет базовые механизмы (retry, jitter, proxy rotation), но для production-нагрузок требуется оркестрация:

  1. Ограничивайте параллельность: Не запускайте сотни браузеров одновременно. Начинайте с малого пула и мониторьте частоту появления challenge. Репозиторий не содержит подтверждённых "безопасных" лимитов запросов для SHEIN.
  2. Сохраняйте сессии: Для периодического мониторинга повторное использование одного CDP-профиля эффективнее постоянной генерации новых.
  3. Добавляйте jitter: Параметры --delay-jitter и --scroll-delay предотвращают синхронные всплески запросов от множества воркеров.
  4. Проверяйте полноту: Сравнивайте количество собранных товаров с полем 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 Copy
{
  "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 Copy
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 для сравнения результатов, что позволяет избежать ложных выводов о "пропаже" товаров из-за временной блокировки скрапера.