Perché Grub
Abbiamo integrato le funzionalità di ogni principale crawler — poi abbiamo aggiunto ciò che nessuno di loro ha.
Crawler self-hostati
Crawler cloud / gestiti
Solo Grub ha Ghost Protocol — fallback automatico basato sulla visione che cattura screenshot delle pagine bloccate ed estrae il contenuto tramite LLM quando il crawling standard fallisce. La prevenzione (Camoufox + proxy + stealth) gestisce il 95% dei blocchi. Ghost Protocol gestisce il resto.
Endpoint API
Crawling principale
Agente (Modalità B)
Gestione dei job
Cache remota
Gestione delle sessioni
Stream live
Mesh
Sistema
Strumenti MCP (grub-crawl.py)
Il bridge MCP espone tutte le funzionalità a qualsiasi host compatibile con MCP:
Moduli interni
Nucleo agente (app/agent/)
Adattatori provider (app/agent/providers/)
Gate delle policy (app/policy/)
Osservabilità (app/observability/)
Livello API
Anti-rilevamento (app/)
| File | Scopo | Stato |
|---|
stealth.py | patch playwright-stealth, blocco dei domini tracker | Completato |
proxy.py | Risoluzione proxy per richiesta con fallback env | Completato |
Mesh (app/mesh/)
Infrastruttura
Macchina a stati dell'agente```
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
Condizioni di arresto applicate a ogni iterazione:
- `max_steps` (predefinito: 12)
- `max_wall_time` (predefinito: 90s)
- `max_failures` (predefinito: 3)
- `no_op_loop` (3 risposte vuote consecutive)
- `policy_denied` (strumento/dominio bloccato)
- `completed` (l'agente risponde con testo)
## Anti-Rilevamento
Tre livelli di anti-rilevamento che si combinano tra loro. La prevenzione ferma i blocchi prima che si verifichino. Il Ghost Protocol se ne occupa in seguito.
### Camoufox Engine
Browser anti-rilevamento pluggable con spoofing delle impronte digitali a livello C++. Nessun trucco manuale sull'user-agent — Camoufox genera impronte digitali realistiche per contesto a livello di browser, incluse canvas, WebGL, font e proprietà del navigator.```bash
# Switch engine (default: chromium)
BROWSER_ENGINE=camoufox
Proxy per richiesta
Instrada il traffico di crawling attraverso pool di proxy residenziali, datacenter o personalizzati. Sovrascrittura per richiesta con valori predefiniti basati su variabili d'ambiente. Configurazione proxy completamente compatibile con 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"
}
}
}'
### Modalità Stealth
Patch `playwright-stealth` opt-in per Chromium (saltate per Camoufox, dove è integrato). Blocca oltre 20 domini di tracciamento/analytics (Google Analytics, DataDome, PerimeterX, ecc.) per ridurre la superficie di fingerprinting.```bash
STEALTH_ENABLED=true
BLOCK_TRACKING_DOMAINS=true
Ghost Protocol
Quando un risultato di crawling segnala un blocco anti-bot (sfida Cloudflare, CAPTCHA,
shell SPA vuota), l'agente può passare alla modalità cloaking:
- Acquisire uno screenshot a pagina intera tramite Playwright
- Inviare l'immagine a un LLM con capacità visive (Claude Sonnet o GPT-4o)
- Estrarre il contenuto dai pixel renderizzati
- Restituire il testo estratto con
render_mode: "ghost" nella traccia
Questo bypassa completamente il rilevamento anti-bot basato su DOM.
Richiede AGENT_GHOST_ENABLED=true. Si attiva automaticamente sui blocchi rilevati quando AGENT_GHOST_AUTO_TRIGGER=true.
Mesh
Agenti che parlano ad agenti. Ogni istanza di Grub è sia un worker che un coordinatore. Il nodo locale scarica sul cloud, il cloud delega al locale. Le chiamate agli strumenti attraversano il filo in modo trasparente.```
Node A (local) Node B (cloud)
┌─────────────┐ ┌─────────────┐
│ AgentEngine │ │ AgentEngine │
│ ↓ │ │ ↓ │
│ MeshDispatcher ──── HTTP ────→ MeshDispatcher │
│ ↓ │ │ ↓ │
│ Dispatcher │ │ Dispatcher │
│ ↓ │ │ ↓ │
│ ToolRegistry │ │ ToolRegistry │
└─────────────┘ └─────────────┘
↕ heartbeat (15s) ↕
└────────────────────────────────┘
**How it works:**
- **Discovery** — i nodi si uniscono tramite una lista di seed peer, poi fanno gossip (1-hop) per conoscere gli altri
- **Heartbeat** — ogni 15s, i nodi scambiano metriche di carico. 3 mancati = non sano. 2 min = rimosso
- **Routing** — MeshDispatcher valuta tutti i nodi in base a carico, località e affinità, quindi instrada le chiamate degli strumenti verso il nodo migliore
- **1-hop max** — solo Nodo A → B, mai A → B → C. Previene i loop di routing
- **Local fallback** — se l'esecuzione remota fallisce, si ripiega sul Dispatcher locale
- **HMAC auth** — tutto il traffico mesh è firmato con un segreto condiviso (SHA-256, 60s TTL)
### Esegui una Mesh a 2 Nodi in Locale```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
Connetti locale 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
### Configurazione manuale```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
Quando la mesh è disabilitata (MESH_ENABLED=false, il default), Grub opera come un normale crawler a singolo nodo con zero overhead di mesh.
Live Stream
Guarda il crawler lavorare in tempo reale. Un pool persistente di istanze Chromium calde trasmette frame della viewport tramite WebSocket o MJPEG.
WebSocket — connettiti e invia comandi interattivi:```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** — mettilo in un tag ``, video istantaneo:```html
<img src="http://localhost:6792/stream/my-session/mjpeg?url=https://example.com" />
Richiede BROWSER_STREAM_ENABLED=true. Ogni istanza di Chromium utilizza ~150-300MB di RAM.
Avvio rapido
Sviluppo locale```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
### Abilita Agent Mode B```bash
# Add to .env
AGENT_ENABLED=true
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
AGENT_PROVIDER=anthropic
Invia una task dell'agente```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-rilevamento (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 (bypass anti-bot)```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 stream del browser```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"
## Configurazione
### Server
- `HOST` (predefinito: 0.0.0.0)
- `PORT` (predefinito: 6792)
- `DEBUG` (predefinito: false)
### Archiviazione
- `STORAGE_PATH` (predefinito: ./storage)
- `RUNNING_IN_CLOUD` (predefinito: false)
- `GCS_BUCKET_NAME`
- `GOOGLE_CLOUD_PROJECT`
### Autenticazione
- `DISABLE_AUTH` (predefinito: false)
- `GNOSIS_AUTH_URL` (predefinito: http://gnosis-auth:5000)
### Motore del browser
- `BROWSER_ENGINE` — chromium | camoufox (predefinito: chromium)
### Crawling
- `MAX_CONCURRENT_CRAWLS` (predefinito: 5)
- `CRAWL_TIMEOUT` (predefinito: 30)
- `ENABLE_JAVASCRIPT` (predefinito: true)
- `ENABLE_SCREENSHOTS` (predefinito: false)
### Proxy
- `PROXY_SERVER` — URL del proxy (es. http://proxy:10001)
- `PROXY_USERNAME`
- `PROXY_PASSWORD`
- `PROXY_BYPASS` — lista di bypass separata da virgole
### Stealth
- `STEALTH_ENABLED` (predefinito: false) — patch playwright-stealth
- `BLOCK_TRACKING_DOMAINS` (predefinito: false) — blocca le richieste di analytics/tracciamento
### Agente (Modalità B)
- `AGENT_ENABLED` (predefinito: false)
- `AGENT_MAX_STEPS` (predefinito: 12)
- `AGENT_MAX_WALL_TIME_MS` (predefinito: 90000)
- `AGENT_MAX_FAILURES` (predefinito: 3)
- `AGENT_ALLOWED_TOOLS` — allowlist separata da virgole
- `AGENT_ALLOWED_DOMAINS` — allowlist separata da virgole
- `AGENT_BLOCK_PRIVATE_RANGES` (predefinito: true)
- `AGENT_REDACT_SECRETS` (predefinito: true)
### Provider LLM
- `AGENT_PROVIDER` — openai | anthropic | ollama (predefinito: openai)
- `OPENAI_API_KEY`
- `OPENAI_MODEL` (predefinito: gpt-4.1-mini)
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_MODEL` (predefinito: claude-3-5-sonnet-latest)
- `OLLAMA_BASE_URL` (predefinito: http://localhost:11434)
- `OLLAMA_MODEL` (predefinito: llama3.1:8b-instruct)
### Protocollo Ghost
- `AGENT_GHOST_ENABLED` (predefinito: false)
- `AGENT_GHOST_AUTO_TRIGGER` (predefinito: true)
- `AGENT_GHOST_VISION_PROVIDER` — eredita da AGENT_PROVIDER
- `AGENT_GHOST_MAX_IMAGE_WIDTH` (predefinito: 1280)
### Mesh
- `MESH_ENABLED` (predefinito: false) — interruttore principale
- `MESH_PEERS` — URL dei peer seed separati da virgole
- `MESH_NODE_NAME` — nome human-readable (predefinito: hostname)
- `MESH_SECRET` — segreto HMAC condiviso per l'autenticazione tra nodi
- `MESH_ADVERTISE_URL` — URL che i peer usano per raggiungere questo nodo
- `MESH_PREFER_LOCAL` (predefinito: true) — preferenza per l'esecuzione locale
- `MESH_HEARTBEAT_INTERVAL_S` (predefinito: 15)
- `MESH_PEER_TIMEOUT_S` (predefinito: 45) — segna come non sano dopo questo intervallo
- `MESH_PEER_REMOVE_S` (predefinito: 120) — rimuovi dalla tabella dei peer dopo questo intervallo
- `MESH_REMOTE_TIMEOUT_MS` (predefinito: 35000) — timeout per le chiamate di strumenti remoti
### Live Stream
- `BROWSER_POOL_SIZE` (predefinito: 1)
- `BROWSER_STREAM_ENABLED` (predefinito: false)
- `BROWSER_STREAM_QUALITY` (predefinito: 25) — qualità JPEG 1-100
- `BROWSER_STREAM_MAX_WIDTH` (predefinito: 854)
- `BROWSER_STREAM_MAX_LEASE_SECONDS` (predefinito: 300)
## Contratto di risposta
`POST /api/markdown` restituisce:
`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`
### Qualità del contenuto
- `blocked` — anti-bot/captcha/challenge
- `empty` — segnale molto basso
- `minimal` — pagine scarne/di errore
- `sufficient` — utilizzabile per la sintesi
Non riassumere a meno che `content_quality == "sufficient"`.
### Difesa da Prompt Injection
- `quarantined=true` significa che l'estrattore ha rilevato testo simile a istruzioni nel contenuto estratto che non era presente nel testo renderizzato visibile della pagina (comune negli abusi di `.sr-only`/contenuto nascosto visivamente).
- Quando è in quarantena, `content_quality` viene declassato a `minimal`, `policy_flags` include `hidden_text_suspected` e `quarantined`, e le uscite `content`/`markdown` vengono vuotate (fail-closed).
### Formato dell'errore```json
{"error": "http_error|validation_error|internal_error", "status": 400, "details": {}}
Benchmarks
Combat arena — benchmark testa a testa contro Crawl4AI, Firecrawl (self-hosted) e Scrapy. Tutti i test vengono eseguiti sulla stessa macchina, con gli stessi URL e nelle stesse condizioni. Grub viene eseguito per primo come baseline, mentre gli altri adapter vengono eseguiti in ordine casuale con un intervallo di 10 secondi tra ciascuno per evitare distorsioni da rate-limiting.
Velocità per singolo URL (ms, più basso è meglio)
Grub vince 4/5 gare di velocità su URL singolo. La conversione Markdown avviene in 0-21ms grazie al motore Rust nativo (grub_md).
Scomposizione delle fasi di Grub (ms lato server)
La navigazione domina; la conversione markdown è sub-millisecondo sulla maggior parte delle pagine grazie al motore Rust.
Throughput in batch (ms, più basso è meglio)
Grub vince 2/3 dimensioni di batch. Costo per URL: 163-312ms (Grub) contro 255-477ms (altri).
Come eseguire```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
## Stato di Sviluppo
### Fase 1: Infrastruttura Core ✅
### Fase 2: Crawling ✅
### Fase 3: Modulo Agente ✅
- [x] Core dell'agente — macchina a stati, tipi, errori (W1)
- [x] Contratto unificato dei tool — dispatcher con timeout/retry (W2)
- [x] Gate delle policy — allowlist dei domini, deny per i range privati, redazione (W3)
- [x] Osservabilità — EventBus, TraceCollector, persistenza di RunSummary (W4)
- [x] Collegamento API — `/api/agent/run`, `/api/agent/status`, JobType.AGENT_RUN (W5)
- [x] Adattatori dei provider — OpenAI, Anthropic, Ollama con fallback (W6)
- [x] Flag di configurazione — impostazioni agent, provider, ghost, stream (W7)
### Fase 4: Protocollo Ghost ✅
- [x] Rilevamento del trigger della modalità cloaking (W8)
- [x] Pipeline di acquisizione screenshot (W8)
- [x] Estrazione visiva tramite Claude/GPT-4o (W8)
- [x] Catena di fallback nel motore (W8)
- [x] Tool Ghost per i chiamanti esterni (W8)
- [x] Tool MCP Ghost + endpoint REST (W8)
### Fase 5: Stream Live del Browser ✅
- [x] Pool di browser persistenti con lease/return (W9)
- [x] Relay dello screencast CDP (W9)
- [x] Endpoint WebSocket con comandi interattivi (W9)
- [x] Stream di fallback MJPEG (W9)
- [x] Endpoint per lo stato dello stream + stato del pool (W9)
### Fase 5.5: Anti-rilevamento ✅
- [x] Motore browser anti-detect Camoufox (W10)
- [x] Proxy per richiesta con fallback env (W10)
- [x] Patch stealth per Chromium (W10)
- [x] Blocco dei domini tracker/analytics (W10)
- [x] Fix del rilevamento del formato vision di Anthropic (W10)
### Fase 6: Coordinatore Mesh ✅
- [x] Scoperta dei peer con gossip (1-hop) (W11)
- [x] Autenticazione inter-nodo HMAC-SHA256 (W11)
- [x] Loop di heartbeat con metriche di carico + retry dei seed (W11)
- [x] MeshDispatcher — routing trasparente dei tool tra i nodi (W12)
- [x] Scoring basato sul carico con bonus di località/affinità (W12)
- [x] Script di deploy — locale, mesh, Cloud Run (W12)
- [x] Topologia mesh a 2 nodi con Docker Compose (W12)
- [x] Landing page incorporata (grub-site) (W12)
### Fase 7: Performance + Hardening
- [x] Motore markdown in Rust (`grub_md`) — estensione nativa PyO3, conversione sub-ms
- [x] Combat arena — benchmark automatici vs Crawl4AI, Firecrawl, Scrapy
- [x] Suite di test unitari — 176 test in tutti i moduli
- [ ] Miglioramenti alla gestione degli errori
- [ ] Monitoraggio e alerting
Vedi [MASTER_PLAN.md](https://github.com/deepbluedynamics/grubcrawler/blob/HEAD/MASTER_PLAN.md) per il piano completo dell'architettura.
## Licenza
Licenza del Progetto Grub Crawler