TL;DR: Блокировка при парсинге — это почти всегда диагноз конкретного слоя: IP, TLS-отпечаток, заголовки, браузерное окружение или поведение. Чинить нужно тот слой, который палится, а не менять всё подряд. База: уважать robots.txt и лимиты ресурса, парсить в разумном темпе, кэшировать. Техника: резидентские прокси, curl_cffi с impersonate вместо голого requests, консистентные заголовки, Playwright там, где без JS не обойтись, и инфраструктура с очередями, backoff и хранением сырого HTML.
Как устроена блокировка: слои, а не одна стена
Анти-бот системы (Cloudflare, Datadome, PerimeterX и другие) не принимают решение по одному признаку. Они собирают сигналы на нескольких уровнях и складывают их в скор. Превысили порог — получили 403 или капчу. Поэтому полезно думать о защите слоями и понимать, на каком именно вас поймали:
- IP-адрес — репутация адреса, его принадлежность датацентру или провайдеру, частота запросов с него.
- TLS-отпечаток — JA3/JA4: какие шифры и расширения ваш клиент предлагает при TLS-рукопожатии.
- HTTP-заголовки — набор, значения и их порядок.
- Браузерное окружение — выполняется ли JavaScript, какие признаки выдаёт headless-браузер.
- Поведение — темп запросов, паттерны навигации, движения мыши, скроллинг.
Классическая ошибка — чинить не тот слой. Крутите User-Agent, а бан идёт по TLS-отпечатку. Меняете прокси, а выдаёт паттерн поведения. Дальше разберём каждый слой отдельно.
Слой 1: IP-адрес
Если с одного IP идут сотни запросов в минуту, никакие хитрые заголовки не спасут — адрес улетит в бан по rate-limit.
Что работает
- Резидентские прокси — IP реальных домашних пользователей. Высокая репутация, подходят для сайтов с серьёзной защитой. Минус — цена и скорость.
- ISP-прокси — адреса, зарегистрированные на интернет-провайдеров, но размещённые в датацентрах. Средний вариант по цене и репутации, часто лучший компромисс для долгих сессий.
- Мобильные прокси — IP мобильных операторов. За одним адресом у оператора сидят тысячи абонентов (CGNAT), поэтому банить такой IP защитным системам невыгодно. Хороши для самых агрессивных целей, но дороги и медленны.
- Датацентровые прокси — дёшево и быстро, но подсети датацентров известны всем анти-бот системам. Годятся только для целей со слабой защитой.
Цен и цифр по провайдерам сознательно не привожу: рынок меняется быстрее, чем устаревает статья, и сравнивать нужно под вашу задачу — гео, объём, требования к сессиям.
Ротация и лимиты
Ротация IP решает половину проблемы. Вторая половина — не превышать разумную частоту запросов даже с «чистого» адреса. Я обычно стартую с 1 запроса в 2–5 секунд на IP и смотрю на долю ошибок 403/429. Если она растёт — снижаю темп, а не увеличиваю пул.
Минимальный пример прокси на httpx (в актуальных версиях параметр называется proxy, а не proxies):
import httpx
with httpx.Client(
proxy="http://user:pass@proxy_host:port",
timeout=15.0,
follow_redirects=True,
) as client:
response = client.get("https://example.com")
print(response.status_code)
Есть и неочевидный момент: старайтесь держать сессию (куки, авторизация) на одном IP. Резкая смена адреса посреди сессии — сам по себе сигнал для защиты.
Слой 2: TLS-отпечаток (JA3/JA4)
Это тот слой, про который забывают чаще всего. При TLS-рукопожатии клиент отправляет ClientHello: список шифров, расширений, версий TLS, эллиптических кривых. Из этого набора строится отпечаток — JA3, а сейчас всё чаще JA4. У Python-библиотеки requests этот отпечаток стабильно отличается от Chrome и Firefox. Защите достаточно сравнить JA3 с заявленным User-Agent: говорите, что вы Chrome, а рукопожатие — от Python. Бан без единого запроса к контенту.
Решение: curl_cffi с impersonate
Библиотека curl_cffi построена на curl-impersonate и умеет воспроизводить TLS-отпечаток реального браузера:
from curl_cffi import requests
response = requests.get(
"https://example.com",
impersonate="chrome",
proxy="http://user:pass@proxy_host:port",
timeout=15,
)
print(response.status_code)
# Сессия с сохранением кукисов
session = requests.Session(impersonate="chrome")
page1 = session.get("https://example.com/catalog")
page2 = session.get("https://example.com/catalog/item/123")
Значение impersonate можно указывать точнее — например, конкретную мажорную версию браузера, которую поддерживает установленная версия curl_cffi. Важно: обновляйте библиотеку, потому что отпечатки старых версий браузеров со временем сами становятся подозрительными.
Моя позиция: для HTTP-парсинга (без рендеринга JS) curl_cffi сегодня — базовый выбор, а не requests. Переписывать с requests на curl_cffi обычно занимает минуты — API намеренно совместим, — а эффект на защищённых сайтах заметен сразу.
Слой 3: заголовки и их порядок
Здесь главное слово — консистентность. Защита смотрит не только на значения, но и на то, что заголовки не противоречат друг другу и TLS-отпечатку.
Главная ошибка старых гайдов — совет «всегда ставьте актуальный User-Agent» с примером вроде Chrome 108. Во-первых, конкретная версия из примера давно снята с поддержки и сама по себе выдаёт бота. Во-вторых, UA не должен жить отдельно от остального: если User-Agent говорит «Chrome на Windows», а TLS-отпечаток — от Python, а в заголовках нет sec-ch-ua и sec-ch-ua-platform, которые Chrome всегда отправляет, — вы подаете три противоречивых сигнала сразу.
Практические правила:
- UA должен соответствовать выбранному
impersonateв curl_cffi и платформе прокси. Единый «персонаж»: один браузер, одна ОС, одна локаль. - Порядок заголовков имеет значение. Браузеры отправляют их в стабильном порядке, а requests — в порядке dict. curl_cffi при impersonate воспроизводит и порядок браузера, ещё один аргумент в его пользу.
Refererдолжен быть осмысленным: при переходе по внутренней ссылке — предыдущая страница того же сайта, а не всегда Google.- Не забивайте заголовки в код вручную по списку из гайдов пятилетней давности. Откройте DevTools реального браузера, скопируйте реальный запрос и сравните со своим.
Слой 4: браузерное окружение
Когда контент рендерится JavaScript или защита проверяет JS-окружение (canvas, WebGL, navigator), чистого HTTP недостаточно. Нужен настоящий браузер — Playwright.
Но headless-браузер «из коробки» заметен: navigator.webdriver равен true, нет плагинов, странное разрешение, упрощённый WebGL. Минимальная разумная конфигурация:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
args=["--disable-blink-features=AutomationControlled"],
)
context = browser.new_context(
locale="ru-RU",
viewport={"width": 1920, "height": 1080},
timezone_id="Europe/Moscow",
)
# Маскируем главный маркер автоматизации
context.add_init_script(
"Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
)
page = context.new_page()
page.goto("https://example.com", wait_until="domcontentloaded")
page.wait_for_timeout(2500) # даём JS отработать
html = page.content()
browser.close()
Нюансы, которые часто упускают:
- Часовой пояс и язык должны совпадать с геолокацией прокси. IP из Германии,
timezone_id="Europe/Moscow"иlocale="ru-RU"— расхождение, которое видно без всякого везения. - Headless-режим сам по себе палится по ряду признаков. Если цель важная, иногда надёжнее запускать браузер с интерфейсом под виртуальным дисплеем, чем городить сложную маскировку headless.
- Браузер дорогой: сотни миллисекунд на HTTP-запрос против 3–10 секунд на страницу с рендерингом. Поэтому разумная архитектура — curl_cffi для всего, что отдаётся без JS, и Playwright только для страниц, где он действительно нужен. Заметную часть «нужен браузер» на практике можно заменить прямыми вызовами внутренних JSON-API сайта — смотрите вкладку Network в DevTools.
Стелс-плагины над Playwright существуют, но я отношусь к ним осторожно: каждый патч — это ещё одна точка, где вы отличаетесь от настоящего браузера, и анти-бот команды активно ищут именно эти паттерны.
Слой 5: поведение
Даже идеальный запрос выдаст бота, если таких запросов тысяча в минуту и все идут строго по прямой: главная → товар → следующий товар.
Что учитывать:
- Случайные задержки, а не фиксированные. Человек не кликает ровно раз в 3 секунды. Плюс редкие длинные паузы — «ушёл за кофе».
- Реалистичная навигация: заход на главную или категорию перед страницей цели, а не только прямые заходы на глубокие URL.
- Суточный ритм: круглосуточная равномерная нагрузка с «человеческих» резидентских IP выглядит странно. Пики днём, провалы ночью — естественнее.
- Объём на сессию: живой пользователь не просматривает 5000 карточек товара подряд с одной сессии.
Если собираете данные для обучения моделей, поведенческий слой важен вдвойне: объёмы большие, паттерн заметнее. Об особенностях сбора обучающих данных и хранения больших корпусов я писал в статье про память AI-систем — там же про то, почему сырые данные стоит сохранять до обработки.
CAPTCHA: индикатор, а не цель
Если вы стабильно получаете капчу — защита уже сложила достаточный скор на нижних слоях. Сервисы решения капч существуют, но делать их основным путём я не рекомендую по двум причинам. Первая — репутационная и юридическая: систематический обход явных мер защиты — это уже не «сбор открытых данных», и к вопросу нужно относиться как к риску, а не как к строчке в бюджете. Вторая — инженерная: решение капчи стоит денег и секунд на каждый запрос, и экономика парсинга быстро перестаёт сходиться.
Правильная реакция на капчу — отступить и пересмотреть слои: снизить темп, сменить тип прокси, проверить консистентность отпечатков, увеличить паузы. Гораздо чаще капча лечится этим, чем подключением распознавания.
А до капчи вообще стоит сделать шаг назад:
- Проверьте robots.txt и условия использования ресурса. Если данные явно закрыты от сбора — возможно, задачу нужно решать иначе.
- Поищите официальный API. Многие площадки отдают данные легально, дёшево и без игр в кошки-мышки.
- Кэшируйте. Если страница обновляется раз в сутки, незачем дёргать её каждый час — вы снижаете и нагрузку на чужой ресурс, и свой профиль риска.
Коротко о юридической рамке (не юридическая консультация)
По российскому праву сбор публичных данных сам по себе не запрещён, но есть границы. Статья 1260 и 1334 ГК РФ защищают базы данных как объекты интеллектуальной собственности: извлечение существенной части базы и её использование могут нарушать права изготовителя. Персональные данные регулируются 152-ФЗ — собирать, хранить и использовать их без законного основания нельзя, даже если они лежат в открытом доступе. Плюс условия использования конкретного сайта и запрет на создание чрезмерной нагрузки. Вывод без юридических советов: публичные неперсональные данные, разумный темп, уважение robots.txt и отсутствие обхода явных технических барьеров — самая безопасная зона. Для коммерческого проекта с сомнительными границами — поговорите с юристом до запуска, а не после претензии.
Инфраструктура: почему «скрипт в цикле» не доживает до продакшна
Техническая сторона парсинга на объёмах — это уже распределённая система, и относиться к ней нужно соответственно.
Очереди и повторы
Задачи (URL) складываются в очередь — Redis, RabbitMQ, что угодно знакомое. Воркеры берут задачи и обрабатывают. Ошибки (429, 403, таймауты) не теряются, а возвращаются в очередь с экспоненциальным backoff:
import random
import time
def fetch_with_retry(fetch_fn, url, max_attempts=5):
for attempt in range(max_attempts):
try:
return fetch_fn(url)
except Exception:
if attempt == max_attempts - 1:
raise
delay = min(2 ** attempt + random.uniform(0, 1), 60)
time.sleep(delay)
Backoff важен вдвойне, если сайт уже начал вас душить: агрессивные повторы только усугубляют бан.
Идемпотентность и дедупликация
Любая задача в очереди может быть выполнена дважды: ретрай, падение воркера, повторная постановка. Запись результата должна быть идемпотентной — upsert по стабильному ключу (URL или ID сущности на сайте), а не insert. Дедупликация URL до постановки в очередь — через множество в Redis или unique-индекс в базе. На больших списках до 30–40% URL оказываются дублями, и каждый дубль — лишний запрос и лишний риск.
Хранение сырого HTML
Сохраняйте сырой ответ (HTML/JSON) до разбора. Причины две. Первая: парсер меняется — сайт обновил вёрстку, вы поправили селекторы, и со сырым HTML переразбор делается без новых запросов к сайту, то есть бесплатно и без риска бана. Вторая: сырой ответ — единственный надёжный источник при отладке «почему поля пустые». Сжатый HTML в объектном хранилище стоит копейки относительно стоимости повторного обхода. Этот же подход — «сырьё сначала, обработка потом» — логично стыкуется с пайплайнами для последующей обработки документов, например RAG-пайплайном для корпоративных документов, а если в собранных страницах сканы — пригодится разбор современного OCR.
Мониторинг
Минимум для эксплуатации: доля успешных ответов по времени, распределение статус-кодов, доля 403/429 по группам прокси, скорость обхода, глубина очереди. Алерт на рост доли ошибок — первый сигнал, что защита адаптировалась и пора чинить конкретный слой, пока не полег весь пул.
FAQ
С какого слоя начинать, если парсер заблокировали?
С диагностики. Получаете 403 сразу, до контента — вероятнее TLS-отпечаток или IP. 403 после нескольких успешных страниц — темп и поведение. Капча — накопленный скор по всем слоям. Таймауты с конкретных прокси — репутация адресов. Не меняйте всё сразу, иначе не поймёте, что сработало.
Нужен ли Playwright всегда?
Нет, и без необходимости использовать его не стоит: он в разы медленнее и дороже по ресурсам. Сначала проверьте, отдаёт ли сайт данные в HTML или через внутренние JSON-эндпоинты — вкладка Network в DevTools. Значительная часть «динамических» сайтов парсится чистым HTTP через curl_cffi.
Сколько прокси нужно?
Зависит от объёма и защищённости цели. Для мониторинга пары сотен страниц в день хватит нескольких адресов. Для обхода каталога в сотни тысяч страниц — нормальный пул резидентских или ISP с ротацией. Начните с малого пула и консервативного темпа и масштабируйте по метрике доли ошибок, а не наугад.
Легально ли это вообще?
Сбор публичных данных при разумной нагрузке, соблюдении robots.txt и без персональных данных — самая безопасная зона. Риски появляются при извлечении существенных частей баз данных, обходе явных барьеров и работе с персональными данными. Для коммерческого проекта границы лучше обсудить с юристом заранее.
Стоит ли брать бесплатные прокси?
Нет. Они медленные, почти всегда уже заблокированы на любых защищённых ресурсах, а главное — вы не контролируете, кто смотрит ваш трафик. Экономия оборачивается потерянным временем и риском утечки сессий.
Заключение
Парсинг без блокировок — это не «список из десяти прокси-советов», а работа по слоям: диагностика, какой слой палится, точечное исправление, метрики успеха. Базовый набор на сегодня: curl_cffi с impersonate вместо requests, консистентные заголовки и UA под выбранный отпечаток, резидентские или ISP-прокси с умеренным темпом, Playwright только там, где без рендеринга не обойтись, и инфраструктура с очередями, backoff, дедупликацией и хранением сырого HTML. И прежде всего — уважение к ресурсу: лимиты, robots.txt, официальные API. Это дешевле и надёжнее любой капчи-фермы.
Нужен стабильный парсинг для вашего AI-продукта — от сбора данных до их обработки и хранения? Напишите мне — обсудим вашу задачу.