Чому парсинг HTML ламається на реальних сайтах
Парсер, який чудово працював на тестовій вибірці з десяти сторінок, ламається на проді вже першого дня. Причина майже завжди одна: статичний HTML з документації та реальна видача — це два різних світи.
Розмітка приходить уже зібраною скриптами, селектори тримаються на класах на кшталт css-1x2y3z, які генерує збирач і змінює при кожному релізі, а сервер віддає контент лише за валідної сесії та правдоподібних заголовків запиту. Розбираємо, як це закривається на практиці.
Динамічна розмітка — найчастіша причина порожніх вибірок. Якщо дані підвантажуються асинхронно, парсер отримує каркас сторінки без потрібних вузлів: селектор формально валідний, але повертає null.
Лікується не «зачекати довше», а роботою з підсумковим станом сторінки — рендерингом через headless-браузер або розбором тих самих запитів, якими фронтенд забирає дані. Другий шлях швидший і стабільніший, але потребує аналізу мережевої активності.
Селектори — друга точка відмови. Прив'язка до автогенерованих класів і до ланцюжків вигляду div > div:nth-child(3) > span означає, що будь-яка правка верстки обнуляє вибірку.
Стійкі парсери спираються на смислові атрибути, мікророзмітку та текст-якорі, а не на позицію в дереві, і явно перевіряють, що знайшли очікувану кількість елементів, — інакше мовчазна деградація замість помилки.
Захист від автоматичних запитів і кодування добивають решту. Обмеження частоти, перевірки на ознаки бота, підміна контенту для підозрілих клієнтів — усе це дає відповідь з кодом успіху та порожнім тілом.
Кодування додає своєї шкоди: без явного зазначення сервер віддає windows-1251 або latin-1, і кирилиця перетворюється на сміття, яке ламає порівняння рядків. Тому в конфігурації парсера кодування задається примусово, а не визначається автоматично.
Практичний висновок: парсер — це не скрипт на один запуск, а сервіс з моніторингом. Ми закладаємо перевірку повноти вибірки, оповіщення при падінні частки успішних розборів і версіонування селекторів, щоб зміна розмітки на джерелі не перетворювалася на тиху втрату даних.
Що дає стійкий парсинг HTML-сторінок
Стійкий парсинг — це коли розбір сторінки не ламається від зміни верстки, а частка успішних розборів тримається на рівні 98-99% замість 70-80% у «одноразових» скриптів.
Різниця видна не в момент запуску, а через місяці: селектори застарівають, структура DOM змінюється, і парсер або продовжує працювати, або тихо віддає порожні поля.
Ми будуємо розбір так, щоб збій було видно одразу і локалізовано в одному місці. Селектори виносяться в окремий шар конфігурації, а не розмазані по коду — правка під нову верстку займає хвилини, а не години регресійного тестування. Нижче — що саме дає такий підхід.
- Частка успішних розборів 98-99% — замість «працює, поки не зламається». Кожен запуск фіксує, скільки сторінок розібрано повністю, скільки частково, скільки впало.
- Підтримка селекторів за хвилини — правила винесені в конфіг, при зміні верстки правиться один файл, а не весь код парсера.
- Зникають цілі класи відмов — порожні поля через зміну класів, дублі записів при повторному проході, зависання на динамічних блоках.
- Повторний прохід по сторінках, що впали — те, що не розібралося, ставиться в чергу і перерозбирається, а не губиться мовчки.
- Перевірка цілісності даних — обов'язкові поля валідуються до запису, биті записи не потрапляють у базу.
- Моніторинг деградації — якщо частка успішних розборів падає нижче порогу, приходить сигнал раніше, ніж це помітить замовник.
Підсумок — парсер перестає бути крихким скриптом і стає частиною інфраструктури: передбачуваною, спостережуваною і ремонтопридатною.
Підходи до розбору розмітки та їхні обмеження
Розбір HTML — це завжди вибір між стійкістю до змін верстки та точністю вилучення.
Селектори за класами та ідентифікаторами працюють швидко й передбачувано, поки розмітка не змінюється: варто розробнику сайту перейменувати обгортку або додати проміжний контейнер, і правило мовчки повертає порожній результат замість помилки.
Тому на практиці ми комбінуємо кілька підходів і закладаємо перевірки, які ловлять деградацію розбору.
| Варіант | Коли підходить | Обмеження |
|---|---|---|
| Статичні селектори за класами та ідентифікаторами | Стабільна верстка, відома структура блоків | Ламається при зміні класів, немає сигналу про помилку |
| Розбір за структурою документа | Списки, таблиці, повторювані картки | Чутливий до вкладеності та порядку вузлів |
| Шаблони під типові блоки | Каталоги, картки товарів, стрічки | Потребує підтримки при зміні макета |
| Ручний розбір тексту | Різнорідні сторінки без чіткої розмітки | Повільно, багато винятків, крихко |
Статичні селектори дають максимальну швидкість, але прив'язані до імен класів, які змінюються найчастіше. Розбір за структурою документа спирається на повторюваність вузлів — він переживає косметичні правки, але ламається при зміні вкладеності.
Шаблони під типові блоки зручні, коли сторінок одного типу багато: правило описує картку один раз і застосовується до сотень сторінок.
Ручний розбір тексту залишаємо як крайній випадок — коли розмітка неоднорідна і формалізувати її дорожче, ніж обробити винятки. Щоб розбір не деградував непомітно, ми перевіряємо, що кожен шаблон повернув непорожній результат, і порівнюємо кількість знайдених блоків з очікуваною. Якщо правило перестає знаходити елементи, це спливає одразу, а не через тижні на порожніх даних.
Як ми вибудовуємо конвеєр розбору сторінок
Розбір починається не з коду, а з інвентаризації джерел: які сторінки віддаємо в розбір, як часто змінюється їхня структура, чи є готові канали вивантаження.
На цьому етапі ми фіксуємо, де розмітка стабільна, а де тримається на крихких селекторах, і вирішуємо, що забираємо зі сторінки, а що тягнемо із супутніх даних.
- Аудит джерел. Дивимося розмітку кожної сторінки, частоту правок шаблону, наявність посторінкової навігації та прихованих блоків, які підвантажуються окремо від основного документа. За підсумком — карта джерел із позначкою, де розбір стійкий, а де потрібен запасний шлях.
- Схема розбору. Проєктуємо структуру даних і правила вилучення: які поля обов'язкові, як обробляємо пропуски, що робимо при зміні верстки. Схема описує не лише «що витягнути», а й як відрізнити коректну сторінку від заглушки чи помилки.
- Налаштування вилучення. Збираємо розбір на Cheerio для серверної віддачі та BeautifulSoup там, де потрібна стійкість до неакуратної розмітки. Селектори прив'язуємо до смислових ознак, а не до порядку елементів, — так правка шаблону не ламає весь конвеєр.
- Перевірка на потоці. Проганяємо розбір на реальному обсязі сторінок, звіряємо вилучені поля з еталонною вибіркою, ловимо розходження за типами сторінок. Окремо перевіряємо поведінку при повільних відповідях і частково завантаженому документі.
- Обробка збоїв. Налаштовуємо повтори, обмеження частоти звернень і журнал помилок із збереженням вихідної сторінки — за нею видно, що саме змінилося в розмітці.
- Передача та регламент. Віддаємо схему розбору, опис полів і правила оновлення селекторів, щоб правки вносилися передбачувано, а не з пам'яті.
- Супровід. Регулярно звіряємо частку успішно розібраних сторінок: падіння показника — сигнал, що джерело змінило шаблон.
Такий порядок дає передбачуваний результат: конвеєр переживає правки верстки, а збої видно на графіку, а не спливають у готових даних.
Що замовник отримує після впровадження
Після впровадження парсинг перестає бути набором скриптів «на одному ноутбуці» і стає керованим сервісом. Нижче — що саме ми передаємо команді замовника, щоб рішення можна було експлуатувати й розвивати без нас.
- Конфігурації розбору. Окремі профілі під кожен тип сторінки: які селектори витягують поля, які атрибути і як нормалізуються значення. Селектори винесені з коду в конфіг — при зміні верстки джерела правка займає хвилини, без релізу застосунку.
- Схеми джерел. Карта: адреса, періодичність обходу, глибина, ліміти запитів, правила обходу пагінації та каталогів. Видно, звідки береться кожен блок даних і що робити при появі нового розділу.
- Правила обробки помилок. Поведінка при зміні структури, таймаутах і порожній відповіді: повтор із затримкою, карантин запису, повідомлення відповідального. Збій одного джерела не зупиняє решту.
- Валідація даних. Перевірки на обов'язкові поля, типи та діапазони значень до запису в сховище — биті записи не потрапляють у базу і не ламають звіти.
- Логи та моніторинг. Журнал прогонів із числом зібраних записів, часткою помилок і часом виконання; алерти на аномалії, щоб проблему бачили до скарги користувача.
- Документація. Опис архітектури, інструкція з додавання джерела та правки селекторів, розбір типових збоїв — новий інженер вводиться в контур без усних пояснень.
- Регламенти експлуатації. Розклад прогонів, порядок оновлення профілів розбору, точки контролю якості та відповідальні за кожен крок.
Пакет самодостатній: команда замовника може змінювати джерела й правила сама, а ми підключаємося на складних змінах. Опишіть задачу — повернемося з оцінкою та планом по вашому контуру.
Кейс: як парсер перестав падати на зміні розмітки
Задача була типовою: збирати картки товарів і ціни із зовнішнього каталогу, який регулярно змінював верстку. Перша версія розбору на Cheerio трималася на жорстких ланцюжках селекторів — div.list > div.item:nth-child(3) > span.price.
Поки джерело не чіпали, все працювало, але кожна правка розмітки на його боці ламала розбір: селектор переставав знаходити вузол, парсер віддавав порожні поля, а частина записів ішла в базу з нульовими цінами.
Частка успішних розборів просідала до 60-70%, і це помічали вже за невірними даними у звітах, а не за падінням процесу — тиха деградація гірша за явну помилку.
Що змінили
Ми переписали вилучення на стійкі ознаки замість позиції в дереві: атрибути data-*, мікророзмітку, семантичні теги та текстові якорі поруч із ціною.
Додали шар перевірки схеми — після розбору документ валідується за очікуваними полями, і якщо частка заповнених падає нижче порогу, запуск позначається як підозрілий, а не пишеться в базу мовчки.
Селектори винесли в конфігурацію окремо від коду: правка джерела тепер лікується зміною правил, а не релізом.
Метрики до і після
| Показник | До | Після |
|---|---|---|
| Частка успішних розборів | 60-70% | 98-99% |
| Час відновлення після зміни розмітки | 1-2 дні | 1-3 години |
| Записів із порожніми полями в базі | регулярно | поодинокі, ловляться валідацією |
Ключове — не сама цифра, а клас відмови. Раніше зміна розмітки означала тиху втрату даних і ручний розбір інциденту; тепер це сигнал у моніторингу й правка конфігурації того ж дня.
Час відновлення впав завдяки тому, що точка відмови локалізована в правилах, а не розмазана по коду розбору.
Чим відрізняється розбір статичної та динамічної розмітки?
Статична розмітка надходить із сервера повністю: HTML уже містить усі потрібні вузли, і парсер читає їх безпосередньо через селектори.
Тут працюють бібліотеки на кшталт Cheerio або BeautifulSoup — вони будують дерево документа й дозволяють забирати дані за класами, атрибутами та структурою вкладеності.
Такий розбір передбачуваний: один запит — одна повна відповідь, а результат залежить лише від того, наскільки стабільні селектори на боці джерела.
Динамічна розмітка збирається вже в браузері: сервер віддає порожній каркас, а дані підвантажуються скриптами й з'являються в DOM через якийсь час після завантаження.
Прямий HTML-парсинг тут поверне порожнечу, тому потрібен рушій, який виконує сценарії та дочікується відмалювання потрібних блоків. Це змінює саму логіку: замість «забрати й розібрати» з'являється очікування конкретного стану сторінки, інакше дані просто не встигнуть з'явитися.
Різниця безпосередньо впливає на стійкість. Статичний парсер ламається переважно при зміні верстки — це помітно одразу й лікується правкою селекторів.
Динамічний залежить ще й від порядку завантаження, підвантаження при прокручуванні та затримок мережі, тому тут закладають повторні спроби, таймаути й перевірку, що елемент справді відмальований, а не просто присутній у розмітці.
На практиці джерела майже завжди змішані: частина даних лежить в HTML, частина надходить окремими запитами.
Ми визначаємо це на етапі розбору й обираємо спосіб для кожного блоку окремо — так парсер не руйнується від однієї підвантажуваної секції й залишається передбачуваним при оновленнях джерела.
Технічні сумніви щодо парсингу HTML
Розбір HTML — найкрихкіша частина збору даних: сторінка змінюється без попередження, а парсер мовчки віддає порожні поля замість помилки. Нижче — інженерні питання, які найчастіше ставлять до старту, і як ми закриваємо кожне.
Як ви переконуєтеся, що зібрані дані коректні?
Будуємо перевірки на трьох рівнях. Схема даних валідує типи та обов'язкові поля — пропущена ціна або порожній заголовок одразу позначаються, а не йдуть у вивантаження.
Контрольні суми та кількість записів звіряються з очікуваним діапазоном: різке падіння числа позицій сигналізує про зламаний селектор. Додатково проганяємо еталонні сторінки, де результат відомий заздалегідь, — розбіжність ловимо до того, як дані потраплять до вас.
Що відбувається, коли сайт-джерело змінює верстку?
Селектори прив'язуємо не до класів виду col-md-7, а до стійких ознак — структури блоків, атрибутів, порядку елементів.
Якщо розмітка все ж поїхала, спрацьовує валідація з попереднього пункту: задача позначається як збійна, надходить повідомлення, збір зупиняється замість запису сміття. Правку селектора вносимо точково, без переписування всього збирача.
Як уникаєте блокувань з боку джерела?
Працюємо в межах розумного навантаження: розподіляємо запити за часом, дотримуємо пауз, використовуємо пул адрес і ротацію заголовків. Де у джерела є офіційний інтерфейс для даних — беремо його замість розбору розмітки. Це знижує і ризик блокування, і крихкість рішення.
Чи можна перевикористати парсер для іншого сайту?
Частково. Логіка обходу, зберігання та перевірок переноситься, а шар вилучення під кожне джерело пишеться окремо — розмітка у всіх своя. Тому нове джерело додається швидше за перше: інфраструктура вже готова, змінюється лише опис того, що і звідки брати.
Опишіть джерела та поля, які потрібні на виході, — повернемося з оцінкою та планом збору.
Обговоримо ваше завдання з розбору сторінок
Розбір HTML — це завдання, де 80% ризику криється в джерелах, а не в коді. Розмітка змінюється без попередження, частина даних підвантажується скриптами, десь стоїть захист від частих запитів, а десь потрібні авторизація та сесія.
Чим раніше ми побачимо реальні сторінки, тим точніше оцінимо обсяг робіт і не закладемо в план зайвого.
Опишіть завдання — повернемося з оцінкою та планом. Що нам важливо зрозуміти на старті:
- які джерела розбираємо: список адрес, типи сторінок, чи є доступ за авторизацією;
- що саме забираємо: конкретні поля, вкладені блоки, таблиці, вкладення та файли;
- як часто і в якому обсязі: разова вигрузка, щоденне оновлення чи потік у реальному часі;
- у якому вигляді потрібен результат: база, таблиця, вигрузка у файл або передача у вашу систему;
- як джерело віддає дані: статична розмітка чи підвантаження через скрипти та запити;
- чи є обмеження: ліміти запитів, вимоги до пауз, правила використання даних;
- що робити при збої: повідомлення, повтор, відкат до останньої коректної вигрузки.
За цими відповідями ми зберемо схему розбору, визначимо точки контролю та закладемо захист від змін розмітки. Ви отримаєте оцінку термінів, план робіт за етапами та зрозумілу картину, що і коли з'явиться на виході.
Надішліть список джерел і приклади сторінок — цього достатньо, щоб повернутися з конкретикою.







