Парсер работал полгода, а сегодня в базу легли пустые строки. Первая мысль — забанили, надо менять прокси. Меняете пул, повышаете качество IP, платите за резидентные вместо датацентровых — а поля всё равно пустые. Потому что причина была не в блокировке: сайт переехал на новую вёрстку, и ваш CSS-селектор больше ни к чему не приклеен.
Это самый дорогой тип поломки, потому что он молчит. Бан виден сразу: 403, капча, редирект. Дрейф вёрстки не роняет ничего — HTTP 200, страница получена, трафик оплачен, а на выходе None. Разберём, как за пять минут отличить одно от другого и как перестать переписывать селекторы вручную после каждого редизайна.
Кому это нужно
Гайд для тех, кто держит парсер в проде дольше одного спринта: мониторинг цен конкурентов, сбор отзывов, агрегация вакансий, ежедневные выгрузки для аналитики. Если вы запускаете скрипт один раз и выбрасываете — дрейф вёрстки вас не касается. Если скрипт крутится по cron месяцами, это ваша основная статья расходов на поддержку.
Масштаб проблемы не выдуманный. По оценке аналитиков GroupBWT, неуправляемые структурные изменения сайтов дают примерно 40–60% повторяющихся затрат на поддержку скраперов в крупных проектах. В отдельных отраслях 10–15% краулеров требуют починки еженедельно — из-за сдвигов DOM, фингерпринтинга и троттлинга эндпоинтов. То есть починка селекторов конкурирует по стоимости с обходом антибота, а внимания ей уделяют в разы меньше.
Фон при этом недобрый: в отчёте Apify «State of Web Scraping 2026» 65,8% респондентов нарастили использование прокси, 58,3% отметили рост расходов на прокси год к году, а больше 62% — общий рост инфраструктурных затрат, главным образом из-за усилившейся защиты от ботов. На этом фоне сжигать оплаченный трафик на страницы, из которых вы всё равно ничего не достаёте, — обидно вдвойне.
Шаг 1. Отличить бан от дрейфа вёрстки
Диагностика занимает несколько минут и делается строго по порядку — иначе легко «починить» не ту поломку.
- Посмотрите на код ответа и размер тела. 403, 429, 503, редирект на страницу проверки или тело в 2–5 КБ — это антибот. HTTP 200 и полновесная страница на 200–800 КБ — сайт вас пустил, дело не в прокси.
- Сохраните сырой HTML на диск и откройте глазами. Не в отладчике, а в браузере. Если товар/отзыв/цена на месте, а парсер их не видит — это дрейф вёрстки.
- Найдите нужный текст поиском по файлу. Есть в HTML, но недоступен по вашему селектору — разметка изменилась. Нет вообще — контент подгружается скриптом, нужен браузерный движок, а не HTTP-запрос.
- Сравните с предыдущей удачной выгрузкой. Диффните старый и новый HTML одного и того же URL: обычно сразу видно новый класс-обёртку, переехавший блок или замену
idнаdata-*. - Проверьте, не отдал ли сайт другую версию страницы. Об этом — отдельно ниже, потому что здесь прокси всё-таки при чём.
Если после третьего пункта диагноз — «разметка поехала», менять прокси бессмысленно. Нужен парсер, который умеет находить элемент, даже когда селектор протух.
Шаг 2. Что такое адаптивные селекторы
Идея простая: вместо того чтобы намертво привязываться к строке .product-card > h3.title, библиотека один раз запоминает «портрет» нужного элемента, а при следующем запуске ищет самый похожий на этот портрет элемент на странице.
Практичнее всего это реализовано в Scrapling — открытом Python-фреймворке Карима Шоаира. Проект вышел в октябре 2024 года и к сентябрю 2026-го собрал более 78 000 звёзд на GitHub; последний на момент написания релиз — v0.4.15 от 23 августа 2026, коммиты идут ежедневно. Требуется Python 3.10+.
Механика адаптивного поиска устроена так. Когда вы вызываете селектор с auto_save=True, Scrapling сохраняет отпечаток элемента:
- имя тега, текст и все атрибуты с их значениями;
- имена тегов соседей;
- путь до элемента — только по именам тегов;
- тег, атрибуты и текст родителя.
Отпечаток кладётся в локальную SQLite-базу и ключуется парой «домен + идентификатор». Домен берётся из URL страницы (или задаётся параметром adaptive_domain), идентификатором по умолчанию служит сама строка селектора — либо ваш собственный, если передать identifier=.
Когда вёрстка меняется и обычный селектор возвращает пустоту, вызов с adaptive=True поднимает сохранённый отпечаток и прогоняет по нему все элементы страницы, считая нечёткую оценку похожести — вплоть до порядка атрибутов. Возвращается элемент с наибольшим совпадением.
Стоит это дёшево. По официальным бенчмаркам проекта парсинг занимает 1,99 мс против 2,01 мс у Parsel/Scrapy, 22,93 мс у PyQuery, 80,57 мс у Selectolax и 1541 мс у BeautifulSoup с lxml. Сам адаптивный поиск похожего элемента — 2,46 мс против 13,3 мс у AutoScraper. То есть страховка от редизайна добавляет к запросу примерно две миллисекунды на фоне сетевой задержки в сотни миллисекунд.
Шаг 3. Ставим и включаем
Установка зависит от того, нужен ли вам браузер:
pip install scrapling— только парсер, без сетевой части. Хватит, если HTML вы получаете своим кодом.pip install "scrapling[fetchers]", затемscrapling install— добавляет фетчеры и скачивает браузеры с зависимостями.- Дополнительно:
[ai]— MCP-сервер,[rag]— обвязка под RAG,[shell]— интерактивная консоль,[all]— всё сразу. Есть готовый образpyd4vinci/scrapling.
Дальше — два прогона. Первый на живой рабочей вёрстке сохраняет отпечаток, второй уже умеет пережить редизайн:
- Прогон-эталон. Создайте объект
Selectorсadaptive=Trueи обязательно передайтеurl— иначе домен уедет в ключ"default", и отпечатки разных сайтов перемешаются. Вызовите нужный селектор сauto_save=True. - Боевой прогон. Тот же селектор, но с
adaptive=True. Пока разметка цела, отработает обычный путь. Когда сломается — включится поиск по похожести. - Логируйте расхождения. Момент, когда обычный селектор дал пусто, а адаптивный что-то нашёл, — это сигнал «сайт переехал», его надо видеть в мониторинге, а не проглатывать молча.
Важная деталь про перезапись: сохранение не накапливается. Повторный auto_save для той же пары «домен + идентификатор» затирает прежний отпечаток. Поэтому эталон снимают на заведомо корректной странице, а не в цикле по всему пулу URL.
Шаг 4. Прокси: где они всё-таки при чём
Мы начали с того, что дрейф вёрстки — не про прокси. Это верно ровно наполовину, и вторая половина стоит денег.
Сайт может отдать вам другую разметку из-за выхода прокси. Локаль, язык и страна меняют шаблон страницы: другой порядок блоков, другие классы, другие форматы цен и дат. Это не гипотеза — в самом Scrapling есть показательное исправление: в версии 0.4.12 из StealthyFetcher убрали принудительную локаль en-US, потому что навязанная локаль расходилась с реальным гео и ломала поведение. Отсюда рабочее правило: снимайте эталонный отпечаток с того же гео, с которого потом собираете данные. Отпечаток, снятый через немецкий IP, будет хуже совпадать со страницей, полученной через бразильский — и вы получите ложную тревогу «сайт сменил вёрстку».
Практические следствия:
- Если пул многострановой — разделяйте отпечатки через
adaptive_domain, задавая туда ключ вида «домен + страна». Иначе одна запись в SQLite будет постоянно перезатираться версиями с разных гео. - Для длинных сценариев держите одну страну и одну сессию на всю задачу. Как это устроить, подробно разобрано в материале про sticky-сессии и когда их использовать.
- A/B-тесты и постепенные раскатки дают одновременно две живые вёрстки на одном домене. Здесь адаптивный поиск особенно полезен: он вытащит элемент из обеих веток, тогда как жёсткий селектор будет случайным образом отдавать пустоту на половине запросов.
Задать прокси в Scrapling можно на всех уровнях. Для быстрых HTTP-запросов у Fetcher и AsyncFetcher есть параметр proxies. Для сессий существует ProxyRotator, которому передаётся список адресов, — он подставляется в FetcherSession. Браузерные DynamicSession и StealthySession принимают прокси на уровне сессии, чтобы IP не менялся посреди сценария.
Ещё одна вещь, которая экономит и пул, и нервы, появилась в версии 0.4.12 — AutoThrottle: библиотека сама подстраивает паузы между запросами под ответы сервера, удваивает задержку при блокировке и уважает заголовок Retry-After. Это ровно то поведение, которое отличает аккуратный сбор от разгона банов наивными ретраями.
Подводные камни
- Не коммитьте SQLite с отпечатками в git. Об этом прямо предупреждает документация. Заодно не используйте
auto_saveна страницах с персональными данными — в отпечаток попадают текст и атрибуты элемента. - Адаптивный поиск — не замена мониторингу. Он вернёт «самый похожий» элемент, а самый похожий не всегда правильный. Если сайт переставил цену со скидкой и цену без скидки местами, похожесть высокая, а данные неверные. Держите проверки на диапазон значений и на долю пустых полей в выгрузке.
- Тихая поломка дороже громкой. Пока селектор молча отдаёт None, пайплайн продолжает ходить по страницам и жечь оплаченный трафик. Про то, во что реально обходится гигабайт, из которого не извлекли данные, есть отдельный разбор — почему цена прокси за ГБ врёт.
- Отпечаток стареет. После подтверждённого редизайна пересними эталон заново, иначе следующая правка сайта будет считаться уже от устаревшего портрета, и точность просядет.
- Если контента нет в HTML вообще — адаптивность не поможет, нужен браузерный фетчер. В 0.4.15 вкладки браузера стали переиспользоваться между запросами, а метод
close_pages()закрывает их принудительно; там же починили зависания в headless-режиме и решение Turnstile перестало зависеть от локали браузера.
Какой тип прокси брать под такую задачу
Выбор диктуется не парсером, а целевым сайтом:
- Датацентровые прокси — для сайтов без серьёзного антибота: документация, госреестры, открытые каталоги, RSS и CSV-фиды (для последних в 0.4.13 добавили
XMLFeedSpiderиCSVFeedSpiderс автоматической распаковкой gzip). Дёшево и быстро, а стабильность разметки здесь обычно выше. - Резидентные прокси — для маркетплейсов, агрегаторов и всего, что персонализирует выдачу по гео. Именно здесь критично снимать эталон и собирать данные с одной страны, иначе вы будете чинить не поломку, а собственную географию.
- Мобильные прокси — когда сайт отдаёт мобильный шаблон и его нужно парсить как есть, либо когда доверие к IP важнее цены за гигабайт.
Коротко
Пустые поля в выгрузке — это два разных диагноза с разным лечением. Сначала проверьте код ответа и сырой HTML: если страница пришла целиком, прокси менять не нужно, поехала разметка. Адаптивные селекторы Scrapling закрывают этот класс поломок за пару миллисекунд на запрос — сохраните отпечаток на рабочей вёрстке, включите adaptive=True в бою и логируйте моменты срабатывания как сигнал о редизайне. И держите гео стабильным: половина «внезапных редизайнов» на практике оказывается другой языковой версией страницы, приехавшей из-за смены страны выхода.
Если стабильное гео и предсказуемая сессия — как раз то, чего вашему парсеру не хватает, посмотрите резидентные прокси ProxyCove: выбор страны, липкие сессии и оплата за реально использованный трафик.
