- Регистрация
- 11.06.2015
- Сообщения
- 258
- Реакции
- 257
- Баллы
- 63
Уважаемые пользователи ZennoPoster!
Представленный визуальный конструктор профилей (ВКП) для парсинга каталогов:
- генерирует несколько кандидатов селекторов;
- проверяет их на карточках текущей и следующей страницы;
- выбирает рабочий вариант и резервные селекторы;
- сохраняет профиль в JSON;
- передаёт профиль сборщику, который извлекает и валидирует данные.
Всё работает локально, без LLM, внешних API и передачи данных на сторонние серверы.
Разметка (JS-оверлей)
↓
Генерация кандидатов (C#)
↓
Тестирование на выборке (C#)
↓
Выбор active + fallback (C#)
↓
Сохранение профиля (JSON)
↓
Сбор данных (C# + XPath)
↓
Валидация полей
↓
Экспорт в CSV
ШАГ 1. Разметка мышкой
Пользователь открывает сайт в ZennoPoster, запускает мастер. Справа появляется панель с ролями:
Пользователь кликает по элементам — 2 раза по каждой роли (кроме «Следующая» — там достаточно 1 раза). Два образца нужны, чтобы система могла сравнить их и найти общие признаки. Один образец не даёт понять, какие классы постоянны, а какие случайны.
ШАГ 2. Генерация селекторов
Система:
Как выбирается активный селектор
Для каждой роли генерируется несколько кандидатов. Все они проходят тестирование на выборке (см. шаг 3), после чего оцениваются по формуле:
Где:
Active = кандидат с максимальным score. Fallback = два следующих по score.
Кандидаты с score ниже порога (coverage < 0.
отбрасываются. Если ни один кандидат не прошёл порог — профиль не сохраняется, пользователь видит ошибку.
Про fallback-селекторы — честно
В текущей версии ВПК fallback = 2 следующих кандидата по score. Они генерируются из того же источника, что active. Пример для
Это слабое резервирование. Если сайт переименует
Настоящий fallback должен быть из независимого источника:
В текущем ВПК это не реализовано. В планах — генерировать fallback из независимых источников.
Пока это означает: профиль переживает переименование одного класса, но не полную смену разметки. Это ограничение, а не универсальное восстановление.
ШАГ 3. Тест на выборке
Каждый кандидат проверяется:
Считаются метрики:
ШАГ 4. Сохранение профиля
Реальный профиль (упрощённый, без полей, которых нет в ВПК):
Профиль намеренно упрощён. В нём нет:
Добавлять поля, которых нет, — плохая идея. Профиль показывает реальное состояние версии ВКП.
ШАГ 5. Сбор данных
Второй шаблон — ВПК_Collector:
Пользователь открывает сайт в ZennoPoster, запускает мастер. Справа появляется панель с ролями:
- Карточка товара
- Название
- Цена
- Ссылка
- Следующая страница
Пользователь кликает по элементам — 2 раза по каждой роли (кроме «Следующая» — там достаточно 1 раза). Два образца нужны, чтобы система могла сравнить их и найти общие признаки. Один образец не даёт понять, какие классы постоянны, а какие случайны.
ШАГ 2. Генерация селекторов
Система:
- собирает общие классы и атрибуты двух образцов;
- отбрасывает хэш-классы (
css-1x2y3z,_a8f3d,sc-bdVaJa); - отбрасывает товарные ID (
product-12345); - формирует список кандидатов.
Как выбирается активный селектор
Для каждой роли генерируется несколько кандидатов. Все они проходят тестирование на выборке (см. шаг 3), после чего оцениваются по формуле:
score = coverage × 0.4
+ field_success × 0.3
+ (1 - ambiguous_ratio) × 0.2
+ source_priority × 0.1
Где:
- coverage — доля карточек, где кандидат нашёл ровно 1 элемент;
- field_success — доля карточек с валидным значением;
- ambiguous_ratio — доля карточек с несколькими совпадениями;
- source_priority — приоритет источника:
data-attr → 1.0
class-combo → 0.9
class → 0.8
tag-class → 0.7
attr-name → 0.6
path-class → 0.5
tag → 0.1
Active = кандидат с максимальным score. Fallback = два следующих по score.
Кандидаты с score ниже порога (coverage < 0.
отбрасываются. Если ни один кандидат не прошёл порог — профиль не сохраняется, пользователь видит ошибку.Про fallback-селекторы — честно
В текущей версии ВПК fallback = 2 следующих кандидата по score. Они генерируются из того же источника, что active. Пример для
.product-card:active: .product-card (score 80)
fallback1: div.product-card (score 70)
fallback2: html > body > ... (score 50)
Это слабое резервирование. Если сайт переименует
.product-card в .item, ни active, ни fallback не сработают.Настоящий fallback должен быть из независимого источника:
[data-testid='product-card']— атрибут, не класс;article.product— семантический тег;[itemprop='itemListElement']— микроразметка.
В текущем ВПК это не реализовано. В планах — генерировать fallback из независимых источников.
Пока это означает: профиль переживает переименование одного класса, но не полную смену разметки. Это ограничение, а не универсальное восстановление.
ШАГ 3. Тест на выборке
Каждый кандидат проверяется:
- на всех карточках текущей страницы;
- на первой следующей странице;
- на повторном запуске после перезагрузки.
Считаются метрики:
- coverage — доля карточек, где найден ровно 1 элемент;
- field_success — доля карточек с валидным значением;
- AMBIGUOUS% — доля карточек с несколькими совпадениями.
ШАГ 4. Сохранение профиля
Реальный профиль (упрощённый, без полей, которых нет в ВПК):
{
"card": {"active": ".product-card", "fallbacks": ["div.product-card"]},
"title": {"active": ".product-title", "fallbacks": ["div.product-title"]},
"price": {"active": ".current-price", "fallbacks": ["div.current-price"]},
"url": {"active": ".product-link", "fallbacks": ["a.product-link"]},
"next_page": {"active": ".next-page", "fallbacks": []}
}
Профиль намеренно упрощён. В нём нет:
- типа поля (text/price/url) — определяется на этапе сбора;
- required — все три поля считаются обязательными;
- extract — извлечение текста или href зашито в сборщик;
- version, site, created_at — в планах.
Добавлять поля, которых нет, — плохая идея. Профиль показывает реальное состояние версии ВКП.
ШАГ 5. Сбор данных
Второй шаблон — ВПК_Collector:
- читает профиль;
- конвертирует CSS-селекторы в XPath;
- находит все карточки по
card.active; - внутри каждой карточки ищет
title,price,url; - для
urlберёт атрибутhref; - формирует CSV с колонками:
page;title;price;url;status.
Для title:
Для price:
Для url:
Что НЕ считается валидным:
Если поле не валидно: карточка получает статус
- элемент найден ровно один раз внутри карточки;
- InnerText не пустой;
- длина текста ≥ 3 символов;
- текст не состоит только из пробелов/знаков препинания.
Для price:
- элемент найден ровно один раз внутри карточки;
- InnerText не пустой;
- содержит цифры;
- соответствует паттерну валюты: ₽ | руб | $ | € | £ | ¥;
- значение > 0.
Для url:
- элемент найден ровно один раз внутри карточки;
- содержит атрибут href;
- href не пустой;
- href не начинается с
javascript:,mailto:,tel:,#; - после resolve относительно base URI — валидный http/https URL.
Что НЕ считается валидным:
- пустая строка;
- текст из одних пробелов;
- цена без цифр («Цена по запросу»);
- отрицательная цена;
- ссылка на якорь (#);
- ссылка на javascript.
Если поле не валидно: карточка получает статус
MISSING_<FIELD>, не попадает в успешные результаты, в CSV пишется пустое значение и статус.Система не понимает семантику
Что система делает:
Что система НЕ делает (в текущей версии):
Как это работает на практике:
На тестовом сайте с
Если бы пользователь кликнул по
Планы: добавить эвристику отсева классов
.current-price vs .old-price. Она выбирает то, что пользователь разметил мышкой на шаге 1.Что система делает:
- проверяет, что элемент найден ровно один раз в карточке;
- проверяет, что значение соответствует паттерну валюты;
- проверяет, что значение > 0.
Что система НЕ делает (в текущей версии):
- не отсеивает
.old-priceпо имени класса; - не анализирует зачёркивание (
text-decoration: line-through); - не сравнивает цены между образцами;
- не проверяет позицию элемента внутри карточки.
Как это работает на практике:
На тестовом сайте с
.old-price + .current-price пользователь кликнул по .current-price. Система выбрала .current-price, потому что пользователь разметил именно его, оба образца содержат .current-price, общий класс — .current-price.Если бы пользователь кликнул по
.old-price, система выбрала бы его. Различение старой/текущей цены — ответственность пользователя.Планы: добавить эвристику отсева классов
old, discount, previous, wasВ текущей версии:
Что реализовано:
Что НЕ реализовано в текущей версии:
Почему: для этой версии достаточно одной страницы, чтобы доказать работоспособность. Пагинация — следующая итерация.
В планах:
- роль
next_pageразмечается пользователем на шаге 1; - селектор сохраняется в профиль;
- сборщик умеет находить кнопку «Следующая»;
- переход выполняется через
nextBtn.Click(); - после перехода — ожидание 2 секунды.
Что реализовано:
- ✓ разметка кнопки «Следующая»;
- ✓ сохранение селектора в профиль;
- ✓ поиск кнопки через XPath;
- ✓ клик и ожидание загрузки;
- ✓ счётчик страниц.
Что НЕ реализовано в текущей версии:
- ✗ автоматический цикл по страницам;
- ✗ защита от повторного сбора одной страницы;
- ✗ определение конца списка (последняя страница);
- ✗ стоп-условия (disabled кнопка, отсутствие кнопки);
- ✗ обработка AJAX-пагинации.
Почему: для этой версии достаточно одной страницы, чтобы доказать работоспособность. Пагинация — следующая итерация.
В планах:
- обернуть сбор в цикл
while (hasNextPage); - стоп по отсутствию кнопки или
disabled; - защита от циклов через сравнение URL;
- сравнение хэша идентификаторов карточек между страницами.
Разметка демо-сайта → профиль:
Сбор данных → CSV (разделитель
:
Пояснение про кавычки:
Цены сохраняются как raw-строка (
card: .product-card
title: .product-title
price: .current-price
url: .product-link
Сбор данных → CSV (разделитель
;, кодировка UTF-
:page;title;price;url;status
1;"Наушники Sony WH-1000XM5";"7 990 ₽";"http://localhost:8081/product/1";VALID
1;"Клавиатура Logitech MX Keys";"6 490 ₽";"http://localhost:8081/product/2";VALID
1;"Мышка Razer DeathAdder";"4 290 ₽";"http://localhost:8081/product/3";VALID
1;"Монитор Dell 27""";"24 990 ₽";"http://localhost:8081/product/4";VALID
1;"Веб-камера Logitech C920";"5 990 ₽";"http://localhost:8081/product/5";VALID
Пояснение про кавычки:
Монитор Dell 27""" — это корректное экранирование CSV. Внутренняя кавычка в названии удваивается — стандарт RFC 4180. Excel открывает такой файл корректно.Цены сохраняются как raw-строка (
"7 990 ₽"). Нормализация до числа — в планах.Проект протестирован на трёх версиях демо-сайта:
Версия A — обычная разметка
Версия B — изменённые классы
→ Доказательство: мастер работает с любыми классами. Профиль для версии A не подходит — система генерирует новый на основе разметки.
Версия C — две цены в карточке (
→ Система выбрала
Честно про тесты: три теста на демо-сайте не доказывают универсальность. Они доказывают, что мастер работает с разными классами и собирает данные корректно.
Для проверки на реальном сайте использовался
Версия A — обычная разметка
.product-card, .product-title, .current-price, .product-link
→ VALID = 5/5
Версия B — изменённые классы
.card, .name, .cost, .link
→ VALID = 5/5
→ Доказательство: мастер работает с любыми классами. Профиль для версии A не подходит — система генерирует новый на основе разметки.
Версия C — две цены в карточке (
.old-price + .current-price)→ VALID = 5/5
→ Система выбрала
.current-price, потому что пользователь кликнул именно по нему.Честно про тесты: три теста на демо-сайте не доказывают универсальность. Они доказывают, что мастер работает с разными классами и собирает данные корректно.
Для проверки на реальном сайте использовался
books.toscrape.com. Результат: после правильной разметки (card=article.product_pod, title=h3 a, price=.price_color, url=h3 a) система собрала 20 книг. Тест подтвердил, что эта версия работает вне демо-сайта, но требует корректной разметки — неверный селектор .btn вместо ссылки на книгу дал MISSING_URL.- ✓ Готов ВПК_Master — визуальная разметка и генерация профиля
- ✓ Готов ВПК_Collector — сбор данных по профилю
- ✓ Реализована визуальная разметка (JS-оверлей)
- ✓ Реализована генерация кандидатов на C#
- ✓ Работает scoring и выбор active + 2 fallback
- ✓ Работает проверка coverage, field_success, AMBIGUOUS%
- ✓ Работает валидация title/price/url
- ✓ Работает XPath-конвертер (CSS → XPath)
- ✓ Работает экспорт в CSV с экранированием
- ✓ Реализован переход по кнопке «Следующая» (один шаг, без цикла)
- ✓ Подготовлено демо — 3 версии сайта, 9 страниц
- ✓ Проведены тесты — A, B, C + books.toscrape.com
О том, что не работает или работает частично:
Технические:
Логические:
Технические:
- требуется доступ к HTML-элементам в браузере (не для closed shadow DOM);
- динамический контент должен успеть загрузиться до разметки;
- SPA (React/Vue с динамической загрузкой) — не поддерживается;
- iframe — не поддерживается;
- бесконечная прокрутка — не поддерживается;
- авторизация — не поддерживается;
- обход капчи — вне задачи.
Логические:
- fallback-селекторы слабо отличаются от active;
- семантики нет — пользователь сам отвечает за выбор элемента;
- система не восстанавливается после полностью изменённой структуры;
- автоматическая адаптация между сайтами — в планах.
Ближайшее:
Среднесрочное:
Долгосрочное:
- автоматический цикл по страницам;
- стоп-условия пагинации (disabled, отсутствие);
- защита от циклов через URL и хэш карточек;
- обработка STRUCTURE_CHANGED при поломке селектора;
- нормализация цены до числа (
7990вместо"7 990 ₽"); - fallback из независимых источников (data-*, article, itemprop).
Среднесрочное:
- экспорт в JSON и Excel;
- клонирование профилей между сайтами одной CMS;
- уведомления в Telegram при поломке селектора;
- история изменений профиля с откатом;
- визуальный отчёт о качестве сбора.
Долгосрочное:
- семантические эвристики для отсева
.old-price; - автоматическая адаптация при изменении вёрстки;
- поддержка SPA через ожидание состояния;
- обработка карточек без цены или ссылки;
- импорт/экспорт профилей между пользователями.
Вопросы для обратной связи:
- какие сайты стоит поддержать в первую очередь?
- какие поля кроме title/price/url нужны?
- нужна ли интеграция с Telegram/Email?
- что стоит доработать в первую очередь?
Спасибо за внимание!
Вложения
Последнее редактирование модератором:


