Почему Grub
Мы объединили возможности каждого крупного краулера — и добавили то, чего нет ни у одного из них.
Самостоятельно размещаемые краулеры
Облачные / управляемые краулеры
Только Grub имеет Ghost Protocol — автоматический резерв на основе зрения, который делает скриншоты заблокированных страниц и извлекает содержимое через LLM, когда обычный краулинг не удаётся. Предотвращение (Camoufox + прокси + стелс) обрабатывает 95% блокировок. Ghost Protocol обрабатывает остальное.
Эндпоинты API
Основной краулинг
Агент (Режим B)
Управление заданиями
Удалённый кеш
Управление сессиями
Живой стрим
Mesh
Система
MCP-инструменты (grub-crawl.py)
MCP-мост открывает все возможности любому MCP-совместимому хосту:
Внутренние модули
Ядро агента (app/agent/)
Адаптеры провайдеров (app/agent/providers/)
Политические шлюзы (app/policy/)
Наблюдаемость (app/observability/)
Уровень API
Анти-обнаружение (app/)
| Файл | Назначение | Статус |
|---|
stealth.py | Стелс-патчи playwright-stealth, блокировка трекерных доменов | Готово |
proxy.py | Разрешение прокси по запросу с резервным env | Готово |
Mesh (app/mesh/)
Инфраструктура
Машина состояний агента```
INIT -> PLAN -> EXECUTE_TOOL -> OBSERVE -> PLAN -> ... -> RESPOND -> STOP
| |
+-- policy_denied ---------------------->+
+-- max_steps / max_wall_time / max_failures -> STOP
+-- no_op_loop (3x empty) ------------> STOP
+-- blocked (ghost trigger) -----------> GHOST -> OBSERVE
Stop conditions enforced every iteration:
- `max_steps` (default: 12)
- `max_wall_time` (default: 90s)
- `max_failures` (default: 3)
- `no_op_loop` (3 consecutive empty responses)
- `policy_denied` (blocked tool/domain)
- `completed` (agent responds with text)
## Антидетекция
Три уровня антидетекции, которые накладываются друг на друга. Профилактика предотвращает блокировки до их появления. Ghost Protocol обрабатывает их после.
### Camoufox Engine
Подключаемый антидетект-браузер с подменой отпечатков на уровне C++. Никаких ручных трюков с User-Agent — Camoufox генерирует реалистичные отпечатки для каждого контекста на уровне браузера, включая canvas, WebGL, шрифты и свойства navigator.```bash
# Switch engine (default: chromium)
BROWSER_ENGINE=camoufox
Прокси по запросу
Направляйте трафик обхода через пулы резидентных, датацентровых или пользовательских прокси. Переопределение для каждого запроса с помощью переменных окружения по умолчанию. Полная конфигурация прокси, совместимая с Playwright.```bash
Env-based default
PROXY_SERVER=http://proxy.example.com:10001
PROXY_USERNAME=your_username
PROXY_PASSWORD=your_password
Or per-request
curl -X POST http://localhost:6792/api/crawl
-H "Content-Type: application/json"
-d '{
"url": "https://example.com",
"options": {
"proxy": {
"server": "http://proxy.example.com:10001",
"username": "your_username",
"password": "your_password"
}
}
}'
### Стелс-режим
Опциональное включение патчей `playwright-stealth` для Chromium (пропускается для Camoufox, где встроено). Блокирует 20+ отслеживающих/аналитических доменов (Google Analytics, DataDome, PerimeterX и т.д.) для уменьшения поверхности цифрового отпечатка.```bash
STEALTH_ENABLED=true
BLOCK_TRACKING_DOMAINS=true
Ghost Protocol
Когда результат обхода сигнализирует о блокировке от ботов (Cloudflare challenge, CAPTCHA,
пустая оболочка SPA), агент может переключиться в режим невидимки:
- Сделать скриншот всей страницы через Playwright
- Отправить изображение LLM с возможностью зрения (Claude Sonnet или GPT-4o)
- Извлечь содержимое из отображаемых пикселей
- Вернуть извлеченный текст с
render_mode: "ghost" в трассировке
Это полностью обходит основанную на DOM защиту от ботов.
Требует AGENT_GHOST_ENABLED=true. Автоматически срабатывает при обнаружении блокировок, когда AGENT_GHOST_AUTO_TRIGGER=true.
Mesh
Агенты общаются с агентами. Каждый экземпляр Grub является одновременно и работником, и координатором. Локальный узел разгружает облако, облако делегирует локальному. Вызовы инструментов прозрачно пересекают провод.```
Node A (local) Node B (cloud)
┌─────────────┐ ┌─────────────┐
│ AgentEngine │ │ AgentEngine │
│ ↓ │ │ ↓ │
│ MeshDispatcher ──── HTTP ────→ MeshDispatcher │
│ ↓ │ │ ↓ │
│ Dispatcher │ │ Dispatcher │
│ ↓ │ │ ↓ │
│ ToolRegistry │ │ ToolRegistry │
└─────────────┘ └─────────────┘
↕ heartbeat (15s) ↕
└────────────────────────────────┘
**Как это работает:**
- **Обнаружение** — узлы присоединяются через список seed-пиров, затем обмениваются (1-hop), чтобы узнать о других
- **Heartbeat** — каждые 15 секунд узлы обмениваются метриками загрузки. 3 пропуска = нездоровый узел. 2 минуты = удаление
- **Маршрутизация** — MeshDispatcher оценивает все узлы по загрузке, местоположению и близости, затем направляет вызовы инструментов на лучший узел
- **Макс. 1-hop** — Узел A → B только, никогда A → B → C. Предотвращает петли маршрутизации
- **Локальное резервирование** — если удалённое выполнение не удалось, используется локальный Dispatcher
- **HMAC-аутентификация** — весь трафик mesh подписывается общим секретом (SHA-256, TTL 60с)
### Запуск Mesh из 2-х узлов локально```bash
# Docker Compose (recommended)
./deploy.sh mesh # Linux/Mac
./deploy.ps1 -Target mesh # Windows
# Verify
curl http://localhost:6792/mesh/peers # Node A sees Node B
curl http://localhost:6793/mesh/peers # Node B sees Node A
Подключение локального приложения к Cloud Run```bash
Deploy to Cloud Run with mesh
./deploy.sh cloudrun latest --mesh-peer http://your-local-ip:6792 --mesh-secret mysecret
Start local node
MESH_ENABLED=true MESH_SECRET=mysecret MESH_PEERS=https://your-cloud-run-url
MESH_ADVERTISE_URL=http://your-local-ip:6792
uvicorn app.main:app --port 6792
### Ручная настройка```bash
# Node A
MESH_ENABLED=true MESH_NODE_NAME=local MESH_SECRET=test123 \
MESH_ADVERTISE_URL=http://localhost:6792 \
uvicorn app.main:app --port 6792
# Node B
MESH_ENABLED=true MESH_NODE_NAME=cloud MESH_SECRET=test123 \
MESH_PEERS=http://localhost:6792 \
MESH_ADVERTISE_URL=http://localhost:8081 \
uvicorn app.main:app --port 8081
Когда mesh отключена (MESH_ENABLED=false, по умолчанию), Grub работает как обычный одноузловой сканер с нулевыми накладными расходами mesh.
Прямая трансляция
Наблюдайте за работой сканера в реальном времени. Постоянный пул прогретых экземпляров Chromium транслирует кадры области просмотра через WebSocket или MJPEG.
WebSocket — подключитесь и отправляйте интерактивные команды:```javascript
const ws = new WebSocket("ws://localhost:6792/stream/my-session?url=https://example.com");
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === "frame") document.getElementById("viewport").src = "data:image/jpeg;base64," + msg.data;
};
// Navigate, click, scroll, type — all over the same socket
ws.send(JSON.stringify({ action: "navigate", url: "https://example.com/pricing" }));
ws.send(JSON.stringify({ action: "click", selector: "#signup-btn" }));
ws.send(JSON.stringify({ action: "scroll", direction: "down" }));
**MJPEG** — поместите его в тег ``, мгновенное видео:```html
<img src="http://localhost:6792/stream/my-session/mjpeg?url=https://example.com" />
Требуется BROWSER_STREAM_ENABLED=true. Каждый экземпляр Chromium использует ~150-300 МБ ОЗУ.
Быстрый старт
Локальная разработка```bash
git clone
cd grub-crawl
cp .env.example .env
pip install -r requirements.txt
uvicorn app.main:app --reload --host 0.0.0.0 --port 6792
### Включить Agent Mode B```bash
# Add to .env
AGENT_ENABLED=true
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
AGENT_PROVIDER=anthropic
Отправить задачу агента```bash
curl -X POST http://localhost:6792/api/agent/run
-H "Content-Type: application/json"
-d '{
"task": "Find the pricing page on example.com and extract plan details",
"max_steps": 10,
"allowed_domains": ["example.com"]
}'
### Docker```bash
# Single node
./deploy.sh local # or ./deploy.ps1 -Target local
# 2-node mesh
./deploy.sh mesh # or ./deploy.ps1 -Target mesh
# Cloud Run
./deploy.sh cloudrun v1.0.0 # or ./deploy.ps1 -Target cloudrun -Tag v1.0.0
# Cloud Run + mesh (connect to local node)
./deploy.sh cloudrun v1.0.0 --mesh-peer http://your-ip:6792 --mesh-secret mykey
Антиобнаружение (Camoufox + Proxy)```bash
Add to .env
BROWSER_ENGINE=camoufox
STEALTH_ENABLED=true
BLOCK_TRACKING_DOMAINS=true
Optional: proxy
PROXY_SERVER=http://proxy.example.com:10001
PROXY_USERNAME=your_username
PROXY_PASSWORD=your_password
### Ghost Protocol (обход антибот-защиты)```bash
# Add to .env
AGENT_GHOST_ENABLED=true
curl -X POST http://localhost:6792/api/agent/ghost \
-H "Content-Type: application/json" \
-d '{"url": "https://blocked-site.com"}'
Прямая трансляция браузера```bash
Add to .env
BROWSER_STREAM_ENABLED=true
BROWSER_POOL_SIZE=2
MJPEG (open in browser)
open "http://localhost:6792/stream/demo/mjpeg?url=https://example.com"
## Конфигурация
### Сервер
- `HOST` (по умолчанию: 0.0.0.0)
- `PORT` (по умолчанию: 6792)
- `DEBUG` (по умолчанию: false)
### Хранилище
- `STORAGE_PATH` (по умолчанию: ./storage)
- `RUNNING_IN_CLOUD` (по умолчанию: false)
- `GCS_BUCKET_NAME`
- `GOOGLE_CLOUD_PROJECT`
### Аутентификация
- `DISABLE_AUTH` (по умолчанию: false)
- `GNOSIS_AUTH_URL` (по умолчанию: http://gnosis-auth:5000)
### Движок браузера
- `BROWSER_ENGINE` — chromium | camoufox (по умолчанию: chromium)
### Обход
- `MAX_CONCURRENT_CRAWLS` (по умолчанию: 5)
- `CRAWL_TIMEOUT` (по умолчанию: 30)
- `ENABLE_JAVASCRIPT` (по умолчанию: true)
- `ENABLE_SCREENSHOTS` (по умолчанию: false)
### Прокси
- `PROXY_SERVER` — URL прокси (например, http://proxy:10001)
- `PROXY_USERNAME`
- `PROXY_PASSWORD`
- `PROXY_BYPASS` — список обхода, разделенный запятыми
### Стелс
- `STEALTH_ENABLED` (по умолчанию: false) — патчи playwright-stealth
- `BLOCK_TRACKING_DOMAINS` (по умолчанию: false) — блокировать запросы аналитики/отслеживания
### Агент (Режим B)
- `AGENT_ENABLED` (по умолчанию: false)
- `AGENT_MAX_STEPS` (по умолчанию: 12)
- `AGENT_MAX_WALL_TIME_MS` (по умолчанию: 90000)
- `AGENT_MAX_FAILURES` (по умолчанию: 3)
- `AGENT_ALLOWED_TOOLS` — разрешённый список, разделённый запятыми
- `AGENT_ALLOWED_DOMAINS` — разрешённый список, разделённый запятыми
- `AGENT_BLOCK_PRIVATE_RANGES` (по умолчанию: true)
- `AGENT_REDACT_SECRETS` (по умолчанию: true)
### Поставщики LLM
- `AGENT_PROVIDER` — openai | anthropic | ollama (по умолчанию: openai)
- `OPENAI_API_KEY`
- `OPENAI_MODEL` (по умолчанию: gpt-4.1-mini)
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_MODEL` (по умолчанию: claude-3-5-sonnet-latest)
- `OLLAMA_BASE_URL` (по умолчанию: http://localhost:11434)
- `OLLAMA_MODEL` (по умолчанию: llama3.1:8b-instruct)
### Протокол Ghost
- `AGENT_GHOST_ENABLED` (по умолчанию: false)
- `AGENT_GHOST_AUTO_TRIGGER` (по умолчанию: true)
- `AGENT_GHOST_VISION_PROVIDER` — наследуется от AGENT_PROVIDER
- `AGENT_GHOST_MAX_IMAGE_WIDTH` (по умолчанию: 1280)
### Mesh
- `MESH_ENABLED` (по умолчанию: false) — главный переключатель
- `MESH_PEERS` — URL-адреса одноранговых узлов, разделённые запятыми
- `MESH_NODE_NAME` — человекочитаемое имя (по умолчанию: hostname)
- `MESH_SECRET` — общий HMAC-секрет для аутентификации между узлами
- `MESH_ADVERTISE_URL` — URL, который используют одноранговые узлы для доступа к этому узлу
- `MESH_PREFER_LOCAL` (по умолчанию: true) — предпочтение локального выполнения
- `MESH_HEARTBEAT_INTERVAL_S` (по умолчанию: 15)
- `MESH_PEER_TIMEOUT_S` (по умолчанию: 45) — отметить как нерабочий после этого
- `MESH_PEER_REMOVE_S` (по умолчанию: 120) — удалить из таблицы одноранговых узлов после этого
- `MESH_REMOTE_TIMEOUT_MS` (по умолчанию: 35000) — тайм-аут для удаленных вызовов инструментов
### Прямая трансляция
- `BROWSER_POOL_SIZE` (по умолчанию: 1)
- `BROWSER_STREAM_ENABLED` (по умолчанию: false)
- `BROWSER_STREAM_QUALITY` (по умолчанию: 25) — качество JPEG 1-100
- `BROWSER_STREAM_MAX_WIDTH` (по умолчанию: 854)
- `BROWSER_STREAM_MAX_LEASE_SECONDS` (по умолчанию: 300)
## Контракт ответа
`POST /api/markdown` возвращает:
`success`, `url`, `final_url`, `status_code`, `markdown`, `markdown_plain`, `content`, `render_mode`, `wait_strategy`, `timings_ms`, `blocked`, `block_reason`, `captcha_detected`, `http_error_family`, `body_char_count`, `body_word_count`, `visible_char_count`, `visible_word_count`, `visible_similarity`, `quarantined`, `quarantine_reason`, `policy_flags`, `content_quality`, `extractor_version`, `normalized_url`, `content_hash`
### Качество содержимого
- `blocked` — анти-бот/капча/вызов
- `empty` — очень слабый сигнал
- `minimal` — тонкие/страницы ошибок
- `sufficient` — пригодно для суммаризации
Не выполнять суммаризацию, если только `content_quality` не равно `"sufficient"`.
### Защита от внедрения подсказок
- `quarantined=true` означает, что экстрактор обнаружил в извлеченном содержимом текст, похожий на инструкции, который отсутствовал в видимом отображаемом тексте страницы (обычно при злоупотреблении `.sr-only`/визуально скрытыми элементами).
- При карантине `content_quality` понижается до `minimal`, `policy_flags` включает `hidden_text_suspected` и `quarantined`, а выходные данные `content`/`markdown` обнуляются (fail-closed).
### Формат ошибки```json
{"error": "http_error|validation_error|internal_error", "status": 400, "details": {}}
Бенчмарки
Арена состязаний — сравнение один на один между Grub, Crawl4AI, Firecrawl (self-hosted) и Scrapy. Все тесты выполняются на одной машине, одних и тех же URL, в одинаковых условиях. Grub запускается первым в качестве базовой линии, остальные адаптеры в случайном порядке с задержкой в 10 секунд между каждым для предотвращения смещения из-за ограничения скорости.
Скорость одиночного URL (мс, чем меньше, тем лучше)
Grub побеждает в 4 из 5 заездов по скорости одиночного URL. Конвертация в Markdown выполняется за 0–21 мс через нативный движок Rust (grub_md).
Разбивка фаз Grub (серверные мс)
Навигация доминирует; конвертация в markdown занимает менее миллисекунды на большинстве страниц благодаря движку Rust.
Пропускная способность пакетов (мс, чем меньше, тем лучше)
Grub побеждает в 2 из 3 размеров пакетов. Стоимость на URL: 163–312 мс (Grub) против 255–477 мс (остальные).
Как запустить```bash
Start Grub
docker compose up -d
Start Firecrawl (optional)
docker compose -f combat/firecrawl-compose.yaml up -d
Install combat deps
pip install crawl4ai scrapy markdownify tabulate
Run the arena
pytest combat/ -m combat -v
Generate report
python -m combat.report
## Статус разработки
### Этап 1: Базовая инфраструктура ✅
### Этап 2: Сканирование ✅
### Этап 3: Модуль агента ✅
- [x] Ядро агента — конечный автомат, типы, ошибки (W1)
- [x] Единый контракт инструментов — диспетчер с тайм-аутом/повтором (W2)
- [x] Политические шлюзы — разрешённые домены, запрет частных диапазонов, редактирование (W3)
- [x] Наблюдаемость — EventBus, TraceCollector, сохранение RunSummary (W4)
- [x] Проводка API — `/api/agent/run`, `/api/agent/status`, JobType.AGENT_RUN (W5)
- [x] Адаптеры провайдеров — OpenAI, Anthropic, Ollama с резервным вариантом (W6)
- [x] Флаги конфигурации — настройки агента, провайдера, ghost, потока (W7)
### Этап 4: Протокол Ghost ✅
- [x] Определение триггера режима маскировки (W8)
- [x] Конвейер захвата снимков экрана (W8)
- [x] Извлечение данных с помощью зрения через Claude/GPT-4o (W8)
- [x] Цепочка резервных вариантов в ядре (W8)
- [x] Инструмент Ghost для внешних вызывающих (W8)
- [x] Ghost MCP инструмент + REST конечная точка (W8)
### Этап 5: Потоковый браузер в реальном времени ✅
- [x] Постоянный пул браузеров с арендой/возвратом (W9)
- [x] Релей передачи экрана через CDP (W9)
- [x] WebSocket конечная точка с интерактивными командами (W9)
- [x] Резервный поток MJPEG (W9)
- [x] Конечные точки состояния потока и состояния пула (W9)
### Этап 5.5: Анти-обнаружение ✅
- [x] Движок браузера с анти-обнаружением Camoufox (W10)
- [x] Прокси на каждый запрос с резервной переменной среды (W10)
- [x] Скрытые патчи для Chromium (W10)
- [x] Блокировка доменов трекеров/аналитики (W10)
- [x] Исправление определения формата зрения Anthropic (W10)
### Этап 6: Координатор Mesh ✅
- [x] Обнаружение пиров с помощью сплетен (1 хоп) (W11)
- [x] Межузловая аутентификация HMAC-SHA256 (W11)
- [x] Цикл сердцебиения с метриками нагрузки и повтором начального узла (W11)
- [x] MeshDispatcher — прозрачная маршрутизация инструментов между узлами (W12)
- [x] Оценка на основе нагрузки с бонусом за локальность/сродство (W12)
- [x] Скрипты развёртывания — локальный, mesh, Cloud Run (W12)
- [x] Топология mesh из 2 узлов в Docker Compose (W12)
- [x] Встроенная страница приветствия (grub-site) (W12)
### Этап 7: Производительность и усиление
- [x] Движок Markdown на Rust (`grub_md`) — нативное расширение PyO3, преобразование за суб-миллисекунды
- [x] Арена боевых испытаний — автоматизированные бенчмарки против Crawl4AI, Firecrawl, Scrapy
- [x] Набор модульных тестов — 176 тестов по всем модулям
- [ ] Улучшения обработки ошибок
- [ ] Мониторинг и оповещения
Полный план архитектуры см. в [MASTER_PLAN.md](https://github.com/deepbluedynamics/grubcrawler/blob/HEAD/MASTER_PLAN.md).
## Лицензия
Лицензия проекта Grub Crawler