Эта статья была полезной?
Как парсить 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: как выгружать поиск, категории и страницы товаров в JSON или CSV, проходить проверку SHEIN через RuCaptcha и сравнивать запуски, отслеживая изменения цен. Мы сосредоточимся на движке Playwright, так как он является рекомендуемым и наиболее стабильным в этом репозитории.
Текущая реализация ориентирована на us.shein.com. Не следует автоматически предполагать, что другие региональные домены используют идентичные структуры данных или эндпоинты.
Что мы соберём
Один консольный инструмент: на вход поисковый запрос, категория или URL товара, на выходе одна строка на товар. Тип страницы определяется по URL:
| Вход | Что возвращает | Для чего |
|---|---|---|
| --query | Товары по поисковому запросу | Анализ трендов и ассортимента |
| --category | Товары категории в том виде, как она называется в URL самого SHEIN | Мониторинг цен по категориям |
| --url | Одна страница товара (из её разметки schema.org JSON-LD) или любой URL поиска или категории | Отслеживание конкретных товаров |
В каждой строке есть sku, category, title, brand, price, currency, original_price, discount_pct, price_source, rating, review_count, store_code, in_stock, is_clearance, quickship, image_url, product_url и scraped_at.
Инструмент diff_runs.py сравнивает два запуска и выдаёт список добавленных, удалённых и подешевевших или подорожавших товаров.
Что понадобится
- Python 3.9 или новее.
- Playwright с Chromium (рекомендуется). Версии для Puppeteer (через pyppeteer, больше не поддерживается) и Selenium работают по тому же сценарию парсинга.
- Отдельное виртуальное окружение для каждого движка: их зависимости конфликтуют.
- Для регулярной работы: API-ключ RuCaptcha и профиль RuCaptcha Browser API. Обычный локальный браузер SHEIN быстро отправляет на проверку или ограничивает. Если вы впервые работаете с API, начните с Quick start.
Установка
bash
git clone https://github.com/2scraper/shein-scraper.git
cd shein-scraper
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-playwright.txt
playwright install chromium
cp .env.example .env
Ключи храните в .env, а не в командной строке: TWOCAPTCHA_KEY — ключ RuCaptcha, SHEIN_CDP_ENDPOINT — профиль Browser API, SHEIN_PROXY — прокси. Если вы используете Docker, команда docker build -t shein-scraper . соберёт образ с Playwright и Chromium.
Быстрый старт
bash
# Поиск
python3 playwright_scraper.py --query "linen pants" --max-results 60
# Категория в том виде, как она выглядит в URL SHEIN, с сохранением в CSV
python3 playwright_scraper.py --category "Women Jeans-c-1934.html" --format csv --out jeans.csv
# Одна страница товара (данные из JSON-LD)
python3 playwright_scraper.py --url "https://us.shein.com/dsbayvkj-p-33704388.html"
# Тот же поиск на других движках
python3 puppeteer_scraper.py --query "summer dress"
python3 selenium_scraper.py --query "summer dress" # только локальный браузер
# Сравнение двух запусков одного поиска
python3 diff_runs.py monday.json tuesday.json
Результаты сохраняются в shein_results.json, рядом появляется shein_results.json.meta.json. Если в .env задан SHEIN_CDP_ENDPOINT, все запуски идут через ваш профиль Browser API без дополнительных флагов.
Значения по умолчанию: --max-results 30, --format json, --max-scrolls 20, --stall-rounds 4, --scroll-delay 1.5 с, --retries 2.
Как работает парсер
Списки товаров берутся из встроенного JSON. Страницы поиска и категорий хранят товары в JavaScript-объекте window.gbRawData. Парсер читает его напрямую, поэтому goods_id и цена — это значения самого SHEIN. По этой же причине нужен браузер: данные собираются скриптами страницы, а не приходят готовым HTML.
Страницы товаров читаются из JSON-LD. Отдельная страница товара разбирается по разметке schema.org ProductGroup или Product.
price_source показывает, откуда взята цена: embedded_json (данные списка), json_ld (страница товара) или json_ld_min_variant (самая низкая цена среди вариантов на странице товара).
Одна страница без номеров. Список товаров SHEIN — это одна страница с бесконечной прокруткой, у её частей нет отдельных адресов. Вместо числа страниц используются --max-results, --max-scrolls и --stall-rounds, а флагов --pages и --concurrency нет: передать часть работы второму процессу нельзя.
Неполные запуски помечаются. SHEIN сообщает, сколько товаров в списке. Если парсер собрал меньше, он предупреждает и помечает запуск как partial, а не делает вид, что получил всё.
Защита от ботов и капча на SHEIN
Подозрительных посетителей SHEIN отправляет вместо страницы на собственный шлюз проверки. Парсер распознаёт оба вида блокировки и обрабатывает их по-разному.
1. /risk/challenge: проверка, которую можно пройти. Сначала парсер нажимает чекбокс «I am human» — это бесплатно. Если SHEIN усложняет проверку, парсер делает скриншот головоломки и отправляет его в RuCaptcha:
- сетку 3×3 «выберите картинки, подходящие к иконке» — как GridTask;
- головоломку «нажмите на иконки по порядку» — как CoordinatesTask.
Парсер кликает по ответу, а если SHEIN его отклоняет, повторяет с новой головоломкой, до --risk-challenge-rounds раз (по умолчанию 5) на одну проверку. Когда решать, задаёт --solve-captcha: off, when-blocked (по умолчанию) или always.
bash
python3 playwright_scraper.py --query "summer dress" --max-results 20 \
--solve-captcha when-blocked --risk-challenge-rounds 5 --max-solves 8
2. /risk/action/limit: лимит запросов, решать нечего. Отправлять его в сервис решения — пустая трата денег. Запуск завершается с кодом 3, а с --rate-limit-cooldown 300 ждёт пять минут и повторяет попытку один раз.
Чего ожидать. В тесте из репозитория 30.09.2026 на пяти новых американских профилях Browser API проверку прошли 3 из 5, каждый с третьего раунда: обычно SHEIN отклоняет первый, а то и второй правильный ответ. Два других профиля получали отказ на каждый ответ, включая правильные, потому что SHEIN пометил сам профиль. В таком случае помогает только другой профиль, а запуск завершается с кодом 3, а не возвращает неверные данные.
Расходы ограничены. --max-solves (по умолчанию 8) задаёт максимум платных решений за запуск; 0 — никогда не платить. Сколько решений потрачено на самом деле, записывается в поле solves_spent в .meta.json.
Рекомендуемая схема: RuCaptcha Browser API. RuCaptcha Browser API даёт удалённый профиль Chromium по CDP. Укажите его endpoint в SHEIN_CDP_ENDPOINT (или передайте через --cdp-endpoint) и добавьте TWOCAPTCHA_KEY. Профиль, который один раз прошёл проверку, сохраняет cookie и обычно работает дальше без новых проверок. Настройка описана в документации Browser API. Selenium не умеет авторизоваться на удалённом endpoint, поэтому для этого используйте Playwright или Puppeteer.
Другие варианты:
- Прокси. --proxy для одного прокси, --proxy-file для ротации пула, --proxy-shuffle для случайного порядка. Форматы: http://login:pass@host:port или host:port:login:pass. Резидентные прокси RuCaptcha подходят для обоих; см. документацию Proxy API. Selenium удаляет пароль прокси с предупреждением.
- Фингерпринты. --fingerprint вместе с --fp-tags и --fp-country подставляет user agent из RuCaptcha Browser fingerprints. Только для локальных браузеров.
- Scraper API. --scraper-api загружает страницу через RuCaptcha Scraper API вместо браузера. Прокрутки нет, поэтому за запуск получается одна страница.
Если делаете свою интеграцию, загляните в документацию API, песочницу для отладки и коды ошибок.
Выходные файлы и коды завершения
Каждый запуск сохраняет файл с результатами и файл .meta.json рядом: статус, stop_reason, число товаров, total_results по данным SHEIN, ограничил ли запуск флаг --max-results, сколько потрачено решений и хеш файла с результатами. Если запуск ничего не собрал, он не пишет ни один из файлов, и предыдущие данные сохраняются. Если пустой файл нужен специально, используйте --allow-empty.
| Код | Значение | Что делать |
|---|---|---|
| 0 | Готово | Данные записаны. |
| 1 | Сбой | Ошибка в парсере. Посмотрите traceback и сообщите о ней. |
| 2 | Ошибка вызова | Исправьте команду. |
| 3 | Блокировка или лимит, строк нет | Возьмите новый профиль Browser API, увеличьте --risk-challenge-rounds или используйте --rate-limit-cooldown. |
| 4 | Нет товаров | Страница загрузилась, но товаров на ней нет. |
| 5 | Контент не получен | Отказал удалённый сервис, страница не загрузилась, или SHEIN сообщил, что товары в списке есть, но ни один не удалось разобрать (stop_reason parser_found_nothing: скорее всего, изменилась вёрстка). |
| 6 | Частичный результат | Строки записаны, но запуск не завершился чисто. Причина в stop_reason: blocked, rate_limited, remote_api_error, failed_pages или rejected_rows. |
Сравнение запусков для мониторинга цен
diff_runs.py сравнивает два полных запуска по sku и выводит добавленные, удалённые и изменившиеся в цене товары:
bash
python3 diff_runs.py monday.json tuesday.json --fail-on-change
Скрипт отказывается от сравнений, которые введут в заблуждение: запуски с разными запросами, рынками или значениями --sort, а также .meta.json, который не соответствует своему файлу. Смена валюты отмечается как currency_changed, а не как изменение цены. Если запуск был ограничен --max-results, товар, пропавший из нового запуска, помечается как left_selection (выпал из первых N), а не как удалённый. С --fail-on-change скрипт завершается с кодом 1 при реальном изменении цены, так что его можно встроить в пайплайн.
Пример вывода
Реальная строка из запуска в репозитории 30.09.2026 (в sample_output.json их три):
json
{
"sku": "458057728",
"source": "shein.com",
"category": "Women Mini Dresses",
"title": "Aloruh Women's Solid Color Sleeveless Mini Dress, Suitable For Beach Vacation,Dresses For Women Summer",
"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-Suitable-For-Beach-Vacation-Dresses-For-Women-Summer-p-458057728.html",
"image_url": "https://img.ltwebstatic.com/v4/j/pi/2026/05/02/f8/17776852344d34295349aad8f43f4fbd32e5a672d8_thumbnail_405x552.jpg",
"scraped_at": "2026-09-30T12:56:49Z",
"original_price": 20.89,
"discount_pct": 38.0,
"rating": 4.62,
"review_count": 1001,
"store_code": "4534970445",
"in_stock": true,
"is_clearance": false,
"quickship": false
}
sku — это goods_id SHEIN, он одинаков для всех цветов и размеров товара. original_price и discount_pct равны null, если скидки нет; rating равен null, если нет отзывов.
Работа с результатами: самые большие скидки
Скрипт читает результаты запуска, проверяет его статус и выводит самые большие скидки среди товаров, у которых достаточно отзывов, чтобы доверять рейтингу:
python
import json
from pathlib import Path
def best_deals(results_file: str, min_reviews: int = 100, top: int = 10) -> None:
path = Path(results_file)
rows = json.loads(path.read_text(encoding="utf-8"))
meta_path = Path(f"{path}.meta.json")
if meta_path.exists():
meta = json.loads(meta_path.read_text(encoding="utf-8"))
print(f"Статус запуска: {meta.get('status')}, строк: {len(rows)}")
deals = [
r for r in rows
if r.get("discount_pct") and r.get("in_stock")
and (r.get("review_count") or 0) >= min_reviews
]
deals.sort(key=lambda r: r["discount_pct"], reverse=True)
for r in deals[:top]:
print(
f"-{r['discount_pct']:.0f}% ${r['price']:.2f} (было ${r['original_price']:.2f}) "
f"{r.get('rating') or '-'}* / {r['review_count']} {r['title'][:60]}"
)
if __name__ == "__main__":
best_deals("shein_results.json")
Ограничения
- Только us.shein.com. Другие страновые сайты SHEIN используют другие локали и не тестировались.
- Товары после первых ~20 не проверены. Страница списка встраивает первую пачку примерно из 20 товаров, а все живые запуски до сих пор запрашивали не больше 20. Парсер прокручивает страницу дальше, но подгружает ли прокрутка новые товары, пока не подтверждено. Если собрано меньше, чем сообщает SHEIN, запуск помечается как partial.
- --sort записывается в результаты, но пока не передаётся в SHEIN. Нужный параметр URL ещё не подтверждён.
- Профили изнашиваются. Данные доступа к Browser API со временем перестают работать; ответ HTTP 401 — сигнал взять новый endpoint. Примерно после десяти запусков подряд профиль может получить лимит на несколько минут.
- Проверку не всегда можно пройти. Помеченный профиль получает отказ даже на правильные ответы.
FAQ
Законно ли парсить SHEIN?
Парсер собирает общедоступные данные о товарах и ценах без входа в аккаунт. Допустимо ли ваше использование, зависит от законов вашей страны и условий использования SHEIN, поэтому проверьте и то и другое и не превышайте разумную частоту запросов. Это не юридическая консультация.
Есть ли у SHEIN публичный API для товаров?
Нет. Парсер читает данные о товарах, которые SHEIN уже встраивает в свои страницы: window.gbRawData в списках и JSON-LD на страницах товаров.
Какую капчу использует SHEIN?
Собственный шлюз проверки: чекбокс «I am human», сетку 3×3 с картинками под иконку и головоломку «нажмите на иконки по порядку». Чекбокс парсер нажимает бесплатно, а две головоломки отправляет в RuCaptcha как GridTask и CoordinatesTask.
Сколько стоит решение капчи за запуск?
Не больше --max-solves платных решений, по умолчанию 8. В тестах проверка проходилась за три раунда, а профиль Browser API, который один раз её прошёл, обычно больше не требует решений.
Почему запуск завершается с кодом 3?
Либо SHEIN ограничил частоту запросов (/risk/action/limit), либо отклоняет все ответы для этого профиля. В первом случае подождите и используйте --rate-limit-cooldown, во втором возьмите новый профиль Browser API.
Заключение
Парсинг SHEIN сводится к двум вещам: читать window.gbRawData вместо вёрстки и по-разному обрабатывать два вида блокировки SHEIN. /risk/challenge решайте через RuCaptcha, /risk/action/limit пережидайте, а для работы используйте профиль Browser API, чтобы один раз пройденная проверка действовала и в следующих запусках. Ограничивайте расходы через --max-solves, стройте автоматизацию на кодах завершения и превращайте запуски в отчёты об изменении цен с помощью diff_runs.py.
С чего начать: зарегистрируйтесь в RuCaptcha, чтобы получить API-ключ, и попробуйте RuCaptcha Browser API.
Исходный код
- Репозиторий: github.com/2scraper/shein-scraper (MIT)
- Все парсеры: github.com/2scraper
Документация
- Quick start: API-ключ, первый запрос, первый результат.
- Grid и Coordinates: два типа задач, которые используют головоломки SHEIN.
- createTask и getTaskResult: два вызова, на которых строится любое решение.
- Коды ошибок и лимиты запросов.
- Документация Browser API: CDP-endpoint и параметры профиля.
- Proxy API: генерация списков резидентных прокси.
- 2captcha-python: официальный Python SDK.
Похожие руководства
- Как парсить Google Play: рейтинги по странам и полная история отзывов.
- Как парсить Avito: решение GeeTest v4 с IP самого браузера.
- Как парсить Transfermarkt: AWS WAF и ловушки пагинации.
- Как парсить Lidl: сетки товаров и цены за единицу.
- Как парсить YouTube: комментарии и метаданные через Innertube API.