Warum Grub
Wir haben Funktionen aus jedem großen Crawler integriert – und dann das hinzugefügt, was keiner von ihnen hat.
Selbst gehostete Crawler
Cloud / Managed Crawler
Nur Grub hat das Ghost Protocol – automatischer vision-basierter Fallback, der blockierte Seiten screenshotet und den Inhalt über LLM extrahiert, wenn normales Crawling fehlschlägt. Prävention (Camoufox + Proxy + Stealth) erledigt 95 % der Blöcke. Das Ghost Protocol erledigt den Rest.
API-Endpunkte
Kern-Crawling
Agent (Modus B)
Job-Verwaltung
Remote-Cache
Sitzungsverwaltung
Live-Stream
Mesh
System
Die MCP-Brücke stellt alle Fähigkeiten jedem MCP-kompatiblen Host zur Verfügung:
Interne Module
Agent-Kern (app/agent/)
Provider-Adapter (app/agent/providers/)
Richtlinien-Gates (app/policy/)
Beobachtbarkeit (app/observability/)
API-Schicht
Anti-Erkennung (app/)
| Datei | Zweck | Status |
|---|
stealth.py | playwright-stealth-Patches, Blockierung von Tracker-Domains | Fertig |
proxy.py | Proxy-Auflösung pro Anfrage mit Umgebungsvariablen-Fallback | Fertig |
Mesh (app/mesh/)
Infrastruktur
Agenten-Zustandsmaschine```
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 aufeinanderfolgende leere Antworten)
- `policy_denied` (blockiertes Tool/Domain)
- `completed` (Agent antwortet mit Text)
## Anti-Erkennung
Drei Ebenen der Anti-Erkennung, die zusammenwirken. Prävention verhindert Blockaden, bevor sie auftreten. Ghost Protocol kümmert sich danach um sie.
### Camoufox Engine
Ansteckbarer Anti-Erkennungs-Browser mit Fingerabdruck-Spoofing auf C++-Ebene. Keine manuellen User-Agent-Tricks — Camoufox generiert realistische Fingerabdrücke pro Kontext auf Browser-Ebene, einschließlich Canvas, WebGL, Schriftarten und Navigator-Eigenschaften.```bash
# Switch engine (default: chromium)
BROWSER_ENGINE=camoufox
Proxy pro Anfrage
Leiten Sie Crawl-Traffic durch Residential-, Rechenzentrums- oder benutzerdefinierte Proxy-Pools. Überschreibung pro Anfrage mit umgebungsbasierten Standardwerten. Vollständige Playwright-kompatible Proxy-Konfiguration.```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"
}
}
}'
### Stealth-Modus
Optionale `playwright-stealth`-Patches für Chromium (bei Camoufox übersprungen, da dort integriert). Blockiert 20+ Tracking-/Analytics-Domains (Google Analytics, DataDome, PerimeterX usw.), um die Fingerabdruckoberfläche zu reduzieren.```bash
STEALTH_ENABLED=true
BLOCK_TRACKING_DOMAINS=true
Ghost-Protokoll
Wenn ein Crawl-Ergebnis einen Anti-Bot-Block signalisiert (Cloudflare-Herausforderung, CAPTCHA,
leere SPA-Shell), kann der Agent in den Tarnmodus wechseln:
- Ein ganzseitiges Screenshot via Playwright aufnehmen
- Das Bild an ein visionsfähiges LLM senden (Claude Sonnet oder GPT-4o)
- Inhalt aus den gerenderten Pixeln extrahieren
- Extrahierten Text mit
render_mode: "ghost" im Trace zurückgeben
Dies umgeht DOM-basierte Anti-Bot-Erkennung vollständig.
Erfordert AGENT_GHOST_ENABLED=true. Automatische Auslösung bei erkannten Blöcken, wenn AGENT_GHOST_AUTO_TRIGGER=true.
Mesh
Agenten, die mit Agenten sprechen. Jede Grub-Instanz ist sowohl Arbeiter als auch Koordinator. Lokaler Knoten lagert an die Cloud aus, Cloud delegiert an lokal. Tool-Aufrufe durchqueren die Leitung transparent.```
Node A (local) Node B (cloud)
┌─────────────┐ ┌─────────────┐
│ AgentEngine │ │ AgentEngine │
│ ↓ │ │ ↓ │
│ MeshDispatcher ──── HTTP ────→ MeshDispatcher │
│ ↓ │ │ ↓ │
│ Dispatcher │ │ Dispatcher │
│ ↓ │ │ ↓ │
│ ToolRegistry │ │ ToolRegistry │
└─────────────┘ └─────────────┘
↕ heartbeat (15s) ↕
└────────────────────────────────┘
**So funktioniert es:**
- **Discovery** — Knoten treten über eine Seed-Peer-Liste bei und tauschen sich dann (1-Hop) aus, um andere kennenzulernen
- **Heartbeat** — Alle 15 Sekunden tauschen Knoten Lastmetriken aus. 3 ausgelassen = fehlerhaft. 2 Minuten = entfernt
- **Routing** — MeshDispatcher bewertet alle Knoten nach Auslastung, Lokalität und Affinität und leitet Tool-Aufrufe an den besten Knoten weiter
- **1-Hop-Maximum** — Knoten A → B nur, niemals A → B → C. Verhindert Routing-Schleifen
- **Lokaler Fallback** — Wenn die Remote-Ausführung fehlschlägt, wird auf den lokalen Dispatcher zurückgefallen
- **HMAC-Authentifizierung** — Der gesamte Mesh-Verkehr ist mit einem gemeinsamen Geheimnis signiert (SHA-256, 60s TTL)
### Führen Sie ein 2-Knoten-Mesh lokal aus```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
Lokales mit Cloud Run verbinden```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
### Manuelle Einrichtung```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
Wenn das Mesh deaktiviert ist (MESH_ENABLED=false, der Standard), arbeitet Grub als normaler Einzelknoten-Crawler ohne Mesh-Overhead.
Live-Stream
Beobachten Sie den Crawler in Echtzeit bei der Arbeit. Ein beständiger Pool von warmen Chromium-Instanzen streamt Viewport-Frames über WebSocket oder MJPEG.
WebSocket — verbinden und interaktive Befehle senden:```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** — legen Sie es in einen ``-Tag, sofortiges Video:```html
<img src="http://localhost:6792/stream/my-session/mjpeg?url=https://example.com" />
Erfordert BROWSER_STREAM_ENABLED=true. Jede Chromium-Instanz verwendet ~150-300MB RAM.
Schnellstart
Lokale Entwicklung```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
### Aktivieren des Agentenmodus B```bash
# Add to .env
AGENT_ENABLED=true
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
AGENT_PROVIDER=anthropic
Agentenaufgabe einreichen```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
Anti-Erkennung (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 (Anti-Bot-Umgehung)```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"}'
Live-Browser-Stream```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"
## Konfiguration
### Server
- `HOST` (Standard: 0.0.0.0)
- `PORT` (Standard: 6792)
- `DEBUG` (Standard: false)
### Speicher
- `STORAGE_PATH` (Standard: ./storage)
- `RUNNING_IN_CLOUD` (Standard: false)
- `GCS_BUCKET_NAME`
- `GOOGLE_CLOUD_PROJECT`
### Authentifizierung
- `DISABLE_AUTH` (Standard: false)
- `GNOSIS_AUTH_URL` (Standard: http://gnosis-auth:5000)
### Browser-Engine
- `BROWSER_ENGINE` — chromium | camoufox (Standard: chromium)
### Crawling
- `MAX_CONCURRENT_CRAWLS` (Standard: 5)
- `CRAWL_TIMEOUT` (Standard: 30)
- `ENABLE_JAVASCRIPT` (Standard: true)
- `ENABLE_SCREENSHOTS` (Standard: false)
### Proxy
- `PROXY_SERVER` — Proxy-URL (z. B. http://proxy:10001)
- `PROXY_USERNAME`
- `PROXY_PASSWORD`
- `PROXY_BYPASS` — durch Komma getrennte Bypass-Liste
### Tarnung
- `STEALTH_ENABLED` (Standard: false) — playwright-stealth Patches
- `BLOCK_TRACKING_DOMAINS` (Standard: false) — Tracking-/Analyse-Anfragen blockieren
### Agent (Modus B)
- `AGENT_ENABLED` (Standard: false)
- `AGENT_MAX_STEPS` (Standard: 12)
- `AGENT_MAX_WALL_TIME_MS` (Standard: 90000)
- `AGENT_MAX_FAILURES` (Standard: 3)
- `AGENT_ALLOWED_TOOLS` — durch Komma getrennte Whitelist
- `AGENT_ALLOWED_DOMAINS` — durch Komma getrennte Whitelist
- `AGENT_BLOCK_PRIVATE_RANGES` (Standard: true)
- `AGENT_REDACT_SECRETS` (Standard: true)
### LLM-Anbieter
- `AGENT_PROVIDER` — openai | anthropic | ollama (Standard: openai)
- `OPENAI_API_KEY`
- `OPENAI_MODEL` (Standard: gpt-4.1-mini)
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_MODEL` (Standard: claude-3-5-sonnet-latest)
- `OLLAMA_BASE_URL` (Standard: http://localhost:11434)
- `OLLAMA_MODEL` (Standard: llama3.1:8b-instruct)
### Ghost-Protokoll
- `AGENT_GHOST_ENABLED` (Standard: false)
- `AGENT_GHOST_AUTO_TRIGGER` (Standard: true)
- `AGENT_GHOST_VISION_PROVIDER` — erbt von AGENT_PROVIDER
- `AGENT_GHOST_MAX_IMAGE_WIDTH` (Standard: 1280)
### Mesh
- `MESH_ENABLED` (Standard: false) — Hauptschalter
- `MESH_PEERS` — durch Komma getrennte Seed-Peer-URLs
- `MESH_NODE_NAME` — menschenlesbarer Name (Standard: Hostname)
- `MESH_SECRET` — gemeinsames HMAC-Geheimnis für die Authentifizierung zwischen Knoten
- `MESH_ADVERTISE_URL` — URL, über die Peers diesen Knoten erreichen
- `MESH_PREFER_LOCAL` (Standard: true) — Tendenz zur lokalen Ausführung
- `MESH_HEARTBEAT_INTERVAL_S` (Standard: 15)
- `MESH_PEER_TIMEOUT_S` (Standard: 45) — danach als ungesund markieren
- `MESH_PEER_REMOVE_S` (Standard: 120) — danach aus der Peer-Tabelle entfernen
- `MESH_REMOTE_TIMEOUT_MS` (Standard: 35000) — Timeout für entfernte Tool-Aufrufe
### Live-Stream
- `BROWSER_POOL_SIZE` (Standard: 1)
- `BROWSER_STREAM_ENABLED` (Standard: false)
- `BROWSER_STREAM_QUALITY` (Standard: 25) — JPEG-Qualität 1–100
- `BROWSER_STREAM_MAX_WIDTH` (Standard: 854)
- `BROWSER_STREAM_MAX_LEASE_SECONDS` (Standard: 300)
## Antwortvertrag
`POST /api/markdown` gibt zurück:
`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`
### Inhaltsqualität
- `blocked` — Anti-Bot/Captcha/Challenge
- `empty` — sehr geringes Signal
- `minimal` — dünne/Fehlerseiten
- `sufficient` — für Zusammenfassungen verwendbar
Nicht zusammenfassen, es sei denn `content_quality == "sufficient"`.
### Schutz vor Prompt-Injection
- `quarantined=true` bedeutet, dass der Extraktor anweisungsähnlichen Text im extrahierten Inhalt erkannt hat, der im sichtbaren gerenderten Text der Seite nicht vorhanden war (häufig bei Missbrauch von `.sr-only`/visuell versteckten Inhalten).
- Bei Quarantäne wird `content_quality` auf `minimal` herabgestuft, `policy_flags` enthält `hidden_text_suspected` und `quarantined`, und die Ausgaben von `content`/`markdown` werden geleert (Fail-Closed).
### Fehlerformat```json
{"error": "http_error|validation_error|internal_error", "status": 400, "details": {}}
Benchmarks
Combat Arena — Kopf-an-Kopf-Benchmarks gegen Crawl4AI, Firecrawl (selbst gehostet) und Scrapy. Alle Tests auf demselben Rechner, denselben URLs, unter denselben Bedingungen. Grub läuft zuerst als Basis, die verbleibenden Adapter in zufälliger Reihenfolge mit 10s Verzögerung zwischen den einzelnen Läufen, um Rate-Limiting-Bias zu vermeiden.
Einzel-URL-Geschwindigkeit (ms, niedriger ist besser)
Grub gewinnt 4/5 Einzel-URL-Geschwindigkeitsrennen. Die Markdown-Konvertierung erfolgt in 0-21 ms über die native Rust-Engine (grub_md).
Grub-Phasenaufschlüsselung (serverseitige ms)
Navigation dominiert; die Markdown-Konvertierung erfolgt auf den meisten Seiten im Submillisekundenbereich dank der Rust-Engine.
Stapeldurchsatz (ms, niedriger ist besser)
Grub gewinnt 2/3 Stapelgrößen. Kosten pro URL: 163-312 ms (Grub) vs. 255-477 ms (andere).
Ausführung```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
## Entwicklungsstatus
### Phase 1: Kerninfrastruktur ✅
### Phase 2: Crawling ✅
### Phase 3: Agentenmodul ✅
- [x] Agentenkern — Zustandsautomat, Typen, Fehler (W1)
- [x] Einheitlicher Tool-Vertrag — Dispatcher mit Timeout/Wiederholung (W2)
- [x] Policy-Gates — Domain-Whitelist, Private-Range-Ablehnung, Redaktion (W3)
- [x] Observability — EventBus, TraceCollector, RunSummary-Persistenz (W4)
- [x] API-Verkabelung — `/api/agent/run`, `/api/agent/status`, JobType.AGENT_RUN (W5)
- [x] Provider-Adapter — OpenAI, Anthropic, Ollama mit Fallback (W6)
- [x] Konfigurationsflags — Agent-, Provider-, Ghost- und Stream-Einstellungen (W7)
### Phase 4: Ghost-Protokoll ✅
- [x] Cloak-Modus-Trigger-Erkennung (W8)
- [x] Screenshot-Erfassungspipeline (W8)
- [x] Vision-Extraktion via Claude/GPT-4o (W8)
- [x] Fallback-Kette in der Engine (W8)
- [x] Ghost-Tool für externe Aufrufer (W8)
- [x] Ghost-MCP-Tool + REST-Endpunkt (W8)
### Phase 5: Live-Browser-Stream ✅
- [x] Beständiger Browser-Pool mit Lease/Return (W9)
- [x] CDP-Screencast-Relay (W9)
- [x] WebSocket-Endpunkt mit interaktiven Befehlen (W9)
- [x] MJPEG-Fallback-Stream (W9)
- [x] Stream-Status- + Pool-Status-Endpunkte (W9)
### Phase 5.5: Anti-Erkennung ✅
- [x] Camoufox Anti-Detect-Browser-Engine (W10)
- [x] Pro-Request-Proxy mit Env-Fallback (W10)
- [x] Stealth-Patches für Chromium (W10)
- [x] Blockierung von Tracker-/Analyse-Domains (W10)
- [x] Fix für Anthropic Vision-Format-Erkennung (W10)
### Phase 6: Mesh-Koordinator ✅
- [x] Peer-Discovery mit Gossip (1-Hop) (W11)
- [x] HMAC-SHA256-Inter-Node-Authentifizierung (W11)
- [x] Heartbeat-Schleife mit Lastmetriken + Seed-Wiederholung (W11)
- [x] MeshDispatcher — transparentes Knoten-übergreifendes Tool-Routing (W12)
- [x] Lastbasiertes Scoring mit Lokalitäts-/Affinitätsbonus (W12)
- [x] Bereitstellungsskripte — lokal, Mesh, Cloud Run (W12)
- [x] Docker-Compose-2-Node-Mesh-Topologie (W12)
- [x] Eingebettete Landing-Page (grub-site) (W12)
### Phase 7: Leistung + Härtung
- [x] Rust-Markdown-Engine (`grub_md`) — PyO3 native Erweiterung, Konvertierung unter ms
- [x] Combat-Arena — automatisierte Benchmarks gegen Crawl4AI, Firecrawl, Scrapy
- [x] Unit-Test-Suite — 176 Tests über alle Module
- [ ] Verbesserungen der Fehlerbehandlung
- [ ] Überwachung und Alarmierung
Siehe [MASTER_PLAN.md](https://github.com/deepbluedynamics/grubcrawler/blob/main/MASTER_PLAN.md) für den vollständigen Architekturplan.
## Lizenz
Grub Crawler Project License