Почему простые HTTP‑запросы не работают
Большинство «классических» скриптов, построенных на requests.get, сталкиваются с тремя типичными проблемами при попытке собрать данные с современных интернет‑магазинов.
- Динамический контент – сайты вроде Beautylish используют React/Next.js, и основной набор данных (цены, описания, отзывы) формируется в браузере после загрузки начального HTML. Обычный GET‑запрос получает лишь пустую оболочку без нужных полей.
- Анти‑бот защита – крупные ритейлеры активно используют fingerprint‑технологии: проверяют наличие «headless»‑признаков, сравнивают IP‑адреса с известными дата‑центрами, анализируют тайминги запросов. Любой отклоняющийся от поведения реального пользователя запрос быстро блокируется.
- Хрупкость разметки – даже небольшое изменение CSS‑класса или структуры DOM ломает парсер, если он полагается на один‑единственный селектор.
Эти ограничения делают невозможным надёжный сбор данных без дополнительного уровня абстракции.
Браузер‑первый подход: Playwright и Puppeteer
Для обхода динамики и скрытности скрипты всё чаще переключаются на управляемые браузеры. Playwright (для Python, Node.js) и Puppeteer (для Node.js) позволяют запускать полностью интерактивный Chromium, Firefox или WebKit, исполнять JavaScript и получать готовый DOM.
- Инициализация браузера – создаётся отдельный контекст с уникальными cookies, user‑agent и viewport, что имитирует реального пользователя.
- Навигация и ожидание – после перехода на страницу скрипт ждёт появления нужных элементов (
page.wait_for_selector) и только затем извлекает данные. - Контроль сетевых запросов – можно фильтровать запросы к рекламным сетям, блокировать трекеры и тем самым ускорять загрузку.
Такой подход превращает парсер из «просто запрос‑ответ» в полноценную браузерную сессию, способную проходить большинство клиент‑сайд проверок.
Стратегия скрытности: резидентные прокси и имитация поведения
Само использование браузера не гарантирует обхода анти‑ботов. Необходимо добавить слой сетевой анонимизации:
- Резидентные прокси – IP‑адреса, принадлежащие реальным домашним пользователям, а не дата‑центрам. Они проходят большинство проверок на «не‑домашний» трафик.
- Ротация IP – при каждом новом запросе или после определённого количества страниц скрипт переключается на другой прокси, избегая частых запросов с одного источника.
- Эмуляция поведения – добавляются случайные задержки между действиями, имитация мышиных движений, скроллинг страницы. Эти «мелочи» снижают вероятность срабатывания детекторов, которые ищут слишком быстрые и однообразные взаимодействия.
В репозитории Beautylish‑scraper эти механизмы реализованы через отдельный модуль ProxyManager, который автоматически подбирает доступные прокси из списка и обновляет их статус при ошибках.
Разделение ответственности в коде
Ключевой принцип профессионального скрейпера – чёткое разделение слоёв. В примере beautylish_scraper_product_data_v1.py реализованы три уровня:
Конфигурационный слой
- Хранит глобальные параметры: API‑ключи для прокси‑провайдеров, таймауты браузера, количество попыток повторного запроса.
- Оформлен в виде единого файла
config.yaml, который легко менять без правки кода. - Позволяет переключать окружения (development, staging, production) через переменные окружения.
Слой обработки данных
- Класс
DataPipelineотвечает за дедупликацию (проверка уникального идентификатора продукта), валидацию (схема pydantic) и сохранение (MongoDB, PostgreSQL или CSV). - Встроенный механизм батч‑записей уменьшает нагрузку на БД и ускоряет запись больших объёмов.
- Логирование всех этапов через
structlog, что упрощает отладку в продакшене.
Слой извлечения (Extractor)
ProductExtractorиспользует Playwright‑страницу, ждёт появления элементов цены, названия, изображений, а затем собирает их в словарь.- Для изменения разметки предусмотрен fallback‑механизм: если основной CSS‑селектор не найден, скрипт пробует альтернативные XPath‑выражения или ищет данные в JSON‑ответах, возвращаемых API‑эндпоинтами.
- Ошибки (таймаут, отсутствие элемента) обрабатываются через retry‑decorator с экспоненциальным бэкофом, что повышает устойчивость к временным сбоям.
Обработка динамического контента и изменение разметки
Для сайтов, где часть данных подгружается через отдельные API‑запросы, скрипт перехватывает сетевые ответы (page.on('response')) и извлекает JSON‑payload напрямую, минуя рендеринг DOM. Это ускоряет процесс и уменьшает нагрузку на браузер.
При обновлении UI парсер автоматически пересобирает карту селекторов: в config.yaml хранится список приоритетных селекторов, а при их неудачном применении скрипт записывает найденные альтернативы в файл selectors_report.json для последующего анализа и обновления.
Надёжность и масштабируемность: повторные попытки, дедупликация, логирование
- Retry‑логика – каждый запрос к странице обёрнут в декоратор, который пере пытается до 5 раз с растущими задержками.
- Дедупликация – перед записью проверяется наличие
product_idв базе; дубликаты игнорируются, а новые записи сразу индексируются. - Мониторинг – интеграция с Prometheus позволяет собирать метрики: количество обработанных страниц, среднее время парсинга, количество ошибок 4xx/5xx.
- Алёрты – при превышении порога ошибок (например, более 10% запросов завершаются таймаутом) система отправляет уведомление в Slack.
Пример реализации на Python
# config.yaml
browser:
timeout: 30
headless: false
proxy:
provider: residential
api_key: YOUR_PROXY_API_KEY
retry:
attempts: 5
backoff_factor: 2
# main.py
import yaml
from playwright.sync_api import sync_playwright
from data_pipeline import DataPipeline
from extractor import ProductExtractor
from proxy_manager import ProxyManager
with open("config.yaml") as f:
cfg = yaml.safe_load(f)
proxy = ProxyManager(cfg["proxy"])
pipeline = DataPipeline()
extractor = ProductExtractor(cfg["browser"])
def fetch_product(url: str):
with sync_playwright() as p:
browser = p.chromium.launch(headless=cfg["browser"]["headless"])
context = browser.new_context(
proxy=proxy.get_current(),
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
page = context.new_page()
page.goto(url, timeout=cfg["browser"]["timeout"] * 1000)
data = extractor.parse(page)
pipeline.save(data)
context.close()
browser.close()
# запуск
for product_url in product_list:
fetch_product(product_url)
Код демонстрирует типичную структуру: конфигурация в отдельном файле, отдельный менеджер прокси, класс‑экстрактор, и пайплайн для сохранения. Такая организация делает скрипт поддерживаемым, масштабируемым и готовым к работе в продакшене без необходимости постоянных правок при изменении сайта.
Эти приёмы показывают, что AI‑генерируемый код уже способен создавать не просто «игрушечные» утилиты, а полноценные, устойчивые к анти‑бот защите решения, способные работать в реальных бизнес‑процессах.