Косметический фильтр вида example.com##.banner-ad не блокирует сетевой запрос — он скрывает уже загруженный элемент страницы через CSS, поэтому рекламный блок исчезает визуально, хотя сам запрос к серверу был выполнен. Именно поэтому парсинг таких правил принципиально отличается от обработки сетевых фильтров: здесь важны доменная привязка, синтаксис CSS-селекторов и специальные маркеры вроде ##, #@# и #?#.
В этой статье разберём, как устроены косметические фильтры, как их корректно парсить из списков EasyList и AdGuard, как применять их вручную в uBlock Origin и что учитывать при написании собственного парсера. Материал подойдёт и пользователям, которые хотят точечно скрыть назойливый элемент, и разработчикам, встраивающим фильтрацию в свой инструмент.
Что такое косметические фильтры и чем они отличаются от сетевых
Косметический фильтр — это правило, которое изменяет отображение страницы после её загрузки: скрывает элементы, сбрасывает стили или внедряет собственный CSS. В отличие от сетевого фильтра, который отменяет сам HTTP-запрос к рекламному серверу, косметический фильтр работает на уровне DOM-дерева.
Типичные задачи, которые решают косметические фильтры:
- 🧹 скрытие рекламных баннеров, загруженных с того же домена, что и контент (сетевой фильтр их не отличит);
- ✂️ удаление всплывающих окон о подписке, cookie-баннеров и плашек «установите наше приложение»;
- 🔓 разблокировка прокрутки страницы, которую сайт отключает через
overflow: hidden; - 🎨 исправление «дыр» в вёрстке после срабатывания сетевых фильтров.
Базовый синтаксис выглядит так: домен##селектор. Например, правило site.ru##.promo-banner скроет элемент с классом promo-banner только на сайте site.ru. Если домен не указан (##.promo-banner), правило считается общим и применяется на всех сайтах — такие правила блокировщики обрабатывают отдельно и осторожнее, чтобы не замедлять каждую страницу.
Синтаксис и типы косметических правил
Перед парсингом нужно понимать, какие маркеры встречаются в списках фильтров. Ошибка в определении типа правила — самая частая причина, почему самописный парсер «съедает» валидные фильтры или применяет исключения как блокирующие правила.
| Маркер | Назначение | Пример |
|---|---|---|
## | Скрытие элемента по CSS-селектору | site.ru##.ad |
#@# | Исключение: отменяет скрытие | site.ru#@#.ad |
#?# | Расширенный селектор ( procedural, :has() и т.п.) | site.ru#?#div:-abp-has(.ad) |
#$# | Внедрение произвольного CSS | site.ru#$#body { overflow: auto !important; } |
#%# | Внедрение скрипта (scriptlet, синтаксис AdGuard) | site.ru#%#//scriptlet(...) |
Обратите внимание на правила-исключения с маркером #@#: они существуют, потому что общие фильтры иногда скрывают полезный контент. При парсинге их нельзя отбрасывать — иначе ваш инструмент будет ломать сайты там, где авторы списков уже предусмотрели отмену.
⚠️ Внимание: правила вида#%#и#$#исполняют произвольный код и стили на странице. Применяйте такие фильтры только из доверенных списков (официальные EasyList, AdGuard, uAssets) — вредоносное правило из непроверенного источника способно воровать данные со страницы.
Как парсить списки косметических фильтров
Парсинг списка фильтров — это построчная обработка текстового файла с классификацией каждой строки. Список вроде EasyList содержит тысячи строк, и среди них перемешаны комментарии, сетевые правила и косметические фильтры всех типов.
Алгоритм разбора одной строки выглядит так:
- 📄 пропустить пустые строки и комментарии — они начинаются с
!или маркера заголовка[Adblock Plus; - 🔍 проверить наличие маркеров
##,#@#,#?#,#$#— если маркер найден, строка является косметическим правилом; - 🌐 отделить часть до маркера (список доменов через запятую) от части после него (селектор или CSS-код);
- ↩️ учесть инверсию доменов: запись
~foo.com##.adозначает «применять везде, кроме foo.com»; - ✅ сохранить результат в структуру вида «домены → селекторы» для быстрого поиска при применении.
Пример простейшего разбора на Python:
COSMETIC_MARKERS = ["#@#", "#?#", "#$#", "#%#", "##"]
def parse_line(line):
line = line.strip()
if not line or line.startswith("!") or line.startswith("["):
return None
for marker in COSMETIC_MARKERS:
if marker in line:
domains, selector = line.split(marker, 1)
return {
"type": marker,
"domains": [d for d in domains.split(",") if d],
"selector": selector,
"is_exception": marker == "#@#",
}
return None # сетевое правило или мусор
Важная деталь: маркер #@# нужно проверять раньше, чем ##, иначе простой поиск подстроки ## не сработает на исключениях некорректно — хотя формально #@# не содержит ##, при посимвольном разборе легко ошибиться. Надёжнее искать маркеры по точному совпадению в порядке убывания длины.
Применение косметических фильтров в uBlock Origin и AdGuard
Если вы не пишете собственный инструмент, а хотите применять готовые или собственные правила, проще всего использовать вкладку пользовательских фильтров. В uBlock Origin откройте панель управления (иконка расширения → значок шестерёнок) и перейдите на вкладку Мои фильтры. В AdGuard аналогичный раздел называется Пользовательские правила.
Порядок добавления собственного правила:
- Откройте целевой сайт и через инспектор браузера (F12) определите CSS-селектор нежелательного элемента.
- Добавьте в пользовательские фильтры строку вида
site.ru##.selector. - Сохраните изменения и обновите страницу — элемент должен исчезнуть.
- Если элемент появляется динамически, проверьте, не требуется ли расширенный селектор.
☑️ Проверка собственного косметического фильтра
У uBlock Origin есть и визуальный инструмент — режим зоркого глаза (element zapper) и пипетка (element picker) в выпадающей панели расширения. Пипетка позволяет кликнуть по элементу на странице, автоматически сгенерировать селектор и сохранить его как косметический фильтр без ручного написания кода. Это безопасный способ: сгенерированное правило можно отредактировать перед сохранением.
Применение фильтров в собственном инструменте
Разработчику, который парсит списки для своего браузерного расширения, скрапера или headless-браузера, нужно решить две задачи: выбрать правила, релевантные текущему домену, и внедрить их на страницу. Первое делается через индекс «домен → правила», второе — через внедрение CSS.
Классический способ применения — сгенерировать таблицу стилей, где каждому селектору назначается display: none !important, и вставить её в документ. В Puppeteer или Playwright это делается через добавление тега style на страницу:
const css = selectors
.map(s => `${s} { display: none !important; }`)
.join("\n");
await page.addStyleTag({ content: css });
Здесь есть тонкость: многие современные сайты генерируют контент динамически, и элемент появляется уже после внедрения стилей. CSS-способ хорош тем, что правило display: none продолжает действовать на новые узлы автоматически — в отличие от одноразового удаления элементов через JavaScript. Поэтому предпочтительнее внедрять стили, а не удалять узлы из DOM вручную.
Для расширенных селекторов (:has(), :-abp-has и прочих procedural-форм) простого CSS недостаточно: часть из них поддерживается современными браузерами нативно (например, :has()), а проприетарные конструкции придётся транслировать или обрабатывать скриптом через MutationObserver. Если ваша задача — базовая фильтрация, разумно ограничиться стандартными селекторами и честно пропускать неподдерживаемые правила, а не пытаться исполнить их некорректно.
Почему глобальные правила (без домена) обрабатывают отдельно
Правила вида ##.ad без указания домена потенциально применяются на каждой странице. Блокировщики выделяют их в отдельный реестр и внедряют выборочно: uBlock Origin, например, опрашивает страницу на наличие классов и id из глобального списка и применяет только совпавшие. Это снижает нагрузку — внедрять тысячи глобальных селекторов на каждый сайт было бы слишком дорого.
Типичные ошибки при парсинге и применении
Первая частая ошибка — игнорирование исключений #@#. Парсер собирает все правила скрытия, но отбрасывает отмены, и в результате инструмент скрывает элементы, которые авторы списков явно разрешили показывать. При применении правил исключения должны обрабатываться с приоритетом над блокирующими.
Вторая проблема — неверная обработка доменных привязок. Правило sub.site.ru##.ad не должно срабатывать на site.ru, а вот правило для site.ru обычно действует и на поддомены — точное поведение зависит от реализации блокировщика, поэтому сверяйтесь с документацией целевого движка (Adblock Plus, uBlock Origin и AdGuard имеют различия в деталях синтаксиса).
⚠️ Внимание: не применяйте косметические фильтры к страницам, где вы работаете с формами, платежами или админ-панелями. Слишком широкий селектор способен скрыть кнопку подтверждения или поле ввода — внешне всё будет выглядеть как «сайт сломался», хотя виноват ваш фильтр. Проверяйте новые правила на безопасных страницах.
Третья ошибка — попытка парсить списки регулярными выражениями «на коленке» без учёта экранирования. В селекторах встречаются символы # внутри атрибутов и escaped-последовательности, и наивный split по маркеру может разрезать селектор в неправильном месте. Ищите первое вхождение маркера и помните, что слева от него может быть пустая строка (глобальное правило).
Производительность и оптимизация
Полные списки содержат десятки тысяч правил, и применять их «в лоб» — внедряя все селекторы на каждую страницу — нельзя: это замедлит рендеринг и увеличит потребление памяти. Рабочий подход — индексация по доменам.
Оптимальная схема выглядит так: при загрузке списка строится словарь, где ключ — домен (или его регистрируемая часть), а значение — массив селекторов. При посещении страницы извлекаются только правила для текущего домена и его родительских зон, плюс выборочно — глобальные правила. Именно так устроены зрелые блокировщики, и для собственного инструмента эта модель тоже подходит.
Дополнительно стоит кешировать скомпилированный CSS для часто посещаемых доменов и дедуплицировать селекторы — в разных списках одни и те же правила встречаются многократно. Дедупликация по строке селектора перед генерацией CSS заметно сокращает итоговую таблицу стилей.
Часто задаваемые вопросы
Чем косметический фильтр отличается от сетевого?
Сетевой фильтр блокирует сам HTTP-запрос к рекламному серверу — ресурс не загружается вообще. Косметический фильтр работает после загрузки страницы: он скрывает или стилизует элементы через CSS. Сетевые правила эффективнее по ресурсам, но не справляются с рекламой, встроенной в основной контент сайта, — там нужны косметические.
Почему моё правило ##.selector не работает на конкретном сайте?
Возможные причины: селектор не совпадает с реальной разметкой (проверьте его в DevTools), элемент создаётся динамически со случайными классами, либо существует исключение #@# в одном из подключённых списков. Также убедитесь, что домен в правиле указан без опечаток и соответствует фактическому хосту страницы.
Можно ли применять косметические фильтры без расширений?
Да. Варианты: пользовательские стили через расширения типа Stylus, внедрение CSS в автоматизированном браузере (Puppeteer, Playwright) через addStyleTag, либо собственный парсер, генерирующий таблицу стилей из списков фильтров. Все три подхода используют один принцип — селекторам назначается display: none.
Безопасно ли подключать сторонние списки фильтров?
Списки из проверенных источников (EasyList, официальные списки AdGuard и uBlock Origin) поддерживаются сообществом и проверяются. Неизвестные списки опасны правилами вида #%# и #$#, которые исполняют код на странице. Перед подключением незнакомого списка просмотрите его на наличие таких маркеров.
Как обрабатывать правила с procedural-селекторами в своём парсере?
Самое надёжное решение для базового инструмента — распознавать маркер #?# и пропускать такие правила с записью в лог. Часть расширенных конструкций (например, :has()) поддерживается современными браузерами нативно и может применяться как обычный CSS, а проприетарные операторы конкретных движков требуют отдельной трансляции либо обработки через MutationObserver.