Ви витрачаєте дні на ручний збір 200 URL конкурентів, виписуючи заголовки та мета-описи. Через місяць ребрендинг — і все спочатку. Краулер вирішує це за хвилини і відтворюється автоматично. Ми — команда з 7-річним досвідом у веб-скрапінгу: реалізували 15+ таких рішень для різних ніш. Одна з частих проблем — неповний збір через JavaScript-рендеринг. Навіть статичний сайт може містити динамічні елементи, які не видно звичайному HTTP-запиту. Краулер з headless-браузером виявляє реальну структуру, включаючи ліниве завантаження. У результаті ви отримуєте повну карту сайту конкурента: всі URL, заголовки, мета-теги, Schema.org. Такий підхід економить до 20 годин роботи на тиждень і дає перевагу в SEO-аналізі.
Які проблеми вирішує краулер для структури сайту?
Частий біль — неповний збір структури через JavaScript-рендеринг, вкладеність URL або канонічні дублі. Наприклад, інтернет-магазин на Vue.js може видавати однаковий контент на різних URL, що спотворює карту сайту. Краулер з headless-браузером виявляє реальну структуру, включаючи динамічні підвантаження.
Ще одна проблема — аналіз Schema.org. Без structured data неможливо оцінити, як конкурент використовує багаті снипети. Краулер збирає JSON-LD та Microdata, дозволяючи копіювати успішні патерни.
Яку архітектуру використовуємо для краулінгу?
Два робочі варіанти: Python + Scrapy/Playwright для складних SPA з лінивим завантаженням, Node.js + Puppeteer/Cheerio для більшості стандартних сайтів. У задачах, де немає динамічного JS-рендерингу, вистачає HTTP-клієнта з HTML-парсером — швидше в 5–10 разів, простіше в деплої.
| Характеристика | HTTP-краулер | Headless-краулер |
|---|---|---|
| Швидкість на сторінку | 0.3–0.8 с | 2–5 с |
| Підтримка JS | Ні | Повна |
| Навантаження на сервер | Низьке | Помірне |
| Складність деплою | Мінімальна | Середня |
Мінімальна Python-реалізація на основі requests + lxml:
import requests
from lxml import html
from urllib.parse import urljoin, urlparse
from collections import deque
import time
from urllib.robotparser import RobotFileParser
class SiteStructureCrawler:
def __init__(self, base_url: str, max_depth: int = 4, delay: float = 1.0):
self.base_url = base_url
self.domain = urlparse(base_url).netloc
self.max_depth = max_depth
self.delay = delay
self.visited: dict[str, dict] = {}
self.queue: deque = deque([(base_url, 0)])
# Перевірка robots.txt
rp = RobotFileParser()
rp.set_url(f'{base_url}/robots.txt')
rp.read()
self.rp = rp
def crawl(self):
session = requests.Session()
session.headers['User-Agent'] = (
'Mozilla/5.0 (compatible; SiteAnalyzer/1.0; +https://example.com/bot)'
)
while self.queue:
url, depth = self.queue.popleft()
if url in self.visited or depth > self.max_depth:
continue
if not self.rp.can_fetch('*', url):
continue # пропускаємо заборонені шляхи
try:
resp = session.get(url, timeout=10, allow_redirects=True)
resp.raise_for_status()
except requests.RequestException as e:
self.visited[url] = {'error': str(e), 'depth': depth}
continue
doc = html.fromstring(resp.content)
doc.make_links_absolute(url)
title = doc.findtext('.//title') or ''
h1 = [h.text_content().strip() for h in doc.cssselect('h1')]
meta_desc_el = doc.cssselect('meta[name="description"]')
meta_desc = meta_desc_el[0].get('content', '') if meta_desc_el else ''
canonical_el = doc.cssselect('link[rel="canonical"]')
canonical = canonical_el[0].get('href', '') if canonical_el else ''
noindex = bool(doc.cssselect('meta[name="robots"][content*="noindex"]'))
# Збір Schema.org та заголовків
schemas = []
for script in doc.cssselect('script[type="application/ld+json"]'):
try:
import json
data = json.loads(script.text_content())
schemas.append(data)
except json.JSONDecodeError:
pass
headings = []
for tag in ['h1', 'h2', 'h3', 'h4']:
for el in doc.cssselect(tag):
headings.append({'tag': tag, 'text': el.text_content().strip()})
links = []
for a in doc.cssselect('a[href]'):
href = a.get('href', '').strip()
parsed = urlparse(href)
if parsed.netloc == self.domain and href not in self.visited:
links.append(href)
if depth + 1 <= self.max_depth:
self.queue.append((href, depth + 1))
self.visited[url] = {
'depth': depth,
'status': resp.status_code,
'title': title.strip(),
'h1': h1,
'meta_description': meta_desc,
'canonical': canonical,
'noindex': noindex,
'internal_links': links,
'content_type': resp.headers.get('Content-Type', ''),
'schema': schemas,
'headings': headings,
}
time.sleep(self.delay)
return self.visited
Робота з JavaScript-рендерингом
Якщо сайт конкурента — SPA (React/Vue/Angular) або використовує lazy-load для основного контенту, звичайний HTTP-краулер поверне порожні сторінки. Тут потрібен headless-браузер:
from playwright.sync_api import sync_playwright
def crawl_spa_page(url: str) -> dict:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until='networkidle', timeout=30000)
title = page.title()
h1_elements = page.query_selector_all('h1')
h1_texts = [el.inner_text() for el in h1_elements]
# Збір всіх посилань після рендерингу
links = page.eval_on_selector_all(
'a[href]',
'els => els.map(e => e.href)'
)
browser.close()
return {'title': title, 'h1': h1_texts, 'links': links}
Playwright додає ~2–5 секунд на сторінку проти 0.3–0.8 секунди для звичайного HTTP. При краулінгу 500+ сторінок це відчутно — використовується лише там, де без нього не обійтися.
Як автоматизувати регулярний краулінг?
Разовий збір даних швидко застаріває. Конкуренти змінюють структуру, додають розділи, переформатовують заголовки. Корисно налаштувати автоматичний запуск раз на тиждень/місяць і порівнювати результати:
def diff_structures(old: dict, new: dict) -> dict:
added = {url: data for url, data in new.items() if url not in old}
removed = {url: data for url, data in old.items() if url not in new}
changed = {}
for url in old:
if url in new:
if old[url].get('title') != new[url].get('title'):
changed[url] = {
'old_title': old[url].get('title'),
'new_title': new[url].get('title'),
}
return {'added': added, 'removed': removed, 'changed': changed}
Чому важливий збір Schema.org?
Structured data — прямий індикатор того, наскільки конкурент вкладається в SEO. Наявність Article, Product, BreadcrumbList, FAQPage дає перевагу у видачі. Краулер фіксує всі типи розмітки, дозволяючи вам перейняти успішні схеми.
Налаштування та запуск краулера
Щоб розпочати збір, виконайте кілька кроків:
- Клонуйте репозиторій з готовим краулером.
- Встановіть залежності:
pip install requests lxml(абоplaywrightдля SPA). - Вкажіть стартовий URL та максимальну глибину обходу.
- Запустіть скрипт:
python crawler.py. - Після завершення отримайте звіт — файл у форматі JSON або CSV.
Результати та автоматизація
Зібрана структура експортується в кілька форматів залежно від задачі:
| Формат | Коли використовувати | Переваги |
|---|---|---|
| JSON | Програмна обробка, інтеграція з API | Повнота даних, вкладеність |
| CSV | Аналіз в Excel/Google Sheets | Простота, сортування |
| SQLite | Регулярний краулінг, історія змін | Швидкі запити, підтримка diff |
import json
import csv
# JSON — для програмної обробки
with open('competitor_structure.json', 'w', encoding='utf-8') as f:
json.dump(crawler.visited, f, ensure_ascii=False, indent=2)
# CSV — для аналізу в Excel/Google Sheets
fieldnames = ['url', 'depth', 'status', 'title', 'meta_description', 'h1', 'noindex', 'canonical']
with open('competitor_structure.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
writer.writeheader()
for url, data in crawler.visited.items():
row = {'url': url, **data}
if isinstance(row.get('h1'), list):
row['h1'] = ' | '.join(row['h1'])
writer.writerow(row)
Що входить в роботу?
- Розробка краулера з урахуванням специфіки вашої ніші: вибір стеку (Python або Node.js), налаштування depth, затримок, robots.txt.
- Інтеграція з headless-браузером для SPA-сайтів.
- Збір всіх URL, заголовків H1-H6, meta-тегів, canonical, noindex, Schema.org.
- Експорт в JSON, CSV або SQLite на ваш вибір.
- Автоматизація запуску через cron/Airflow зі збереженням історії змін.
- Документація з експлуатації та консультація з аналізу результатів.
Строки та гарантії
Базовий краулер (HTTP, без SPA) з експортом в CSV/JSON — від 1 до 2 робочих днів. З підтримкою JavaScript-рендерингу, збором Schema.org, diff-порівнянням та SQLite-сховищем — від 3 до 4 днів. Інтеграція з планувальником та повідомленнями при змінах — ще від 1 до 2 днів.
Ми гарантуємо, що краулер поважає robots.txt (Robots.txt) — обов'язкова перевірка перед кожним запитом. Затримка між запитами (мінімум 1 секунда) запобігає блокуванню по IP. Для регулярного краулінгу використовуємо ротацію User-Agent та проксі.
Типові помилки при розробці краулера:
- Ігнорування robots.txt — ризик блокування IP.
- Занадто швидкий краулінг (delay < 1 сек) — бан від сервера.
- Пропуск перевірки canonical — дублі роздувають структуру.
- Відсутність обробки циклічних посилань — нескінченний обхід.
- Неврахування пагінації (page/2, page/3) — неповний збір.
Зв'яжіться з нами для консультації. Замовте розробку краулера під вашу нішу. Отримайте безкоштовний аналіз ваших конкурентів та рекомендації з краулінгу.







