Вы тратите дни на ручной сбор 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, дифф-сравнением и SQLite-хранилищем — от 3 до 4 дней. Интеграция с планировщиком и уведомлениями при изменениях — ещё от 1 до 2 дней.
Мы гарантируем, что краулер уважает robots.txt (Robots.txt) — обязательная проверка перед каждым запросом. Задержка между запросами (минимум 1 секунда) предотвращает блокировку по IP. Для регулярного краулинга используем ротацию User-Agent и прокси.
Типичные ошибки при разработке краулера:
- Игнорирование robots.txt — риск блокировки IP.
- Слишком быстрый краулинг (delay < 1 сек) — бан от сервера.
- Пропуск проверки canonical — дубли раздувают структуру.
- Отсутствие обработки циклических ссылок — бесконечный обход.
- Неучёт пагинации (page/2, page/3) — неполный сбор.
Свяжитесь с нами для консультации. Закажите разработку краулера под вашу нишу. Получите бесплатный анализ ваших конкурентов и рекомендации по краулингу.







