Por qué Grub
Integramos funciones de todos los rastreadores principales y luego añadimos lo que ninguno tiene.
Rastreadores auto-alojados
Rastreadores en la nube / gestionados
Solo Grub tiene el Protocolo Ghost: respaldo automático basado en visión que captura páginas bloqueadas y extrae contenido mediante LLM cuando el rastreo estándar falla. La prevención (Camoufox + proxy + stealth) maneja el 95% de los bloqueos. El Protocolo Ghost maneja el resto.
Endpoints de API
Rastreo principal
Agente (Modo B)
Gestión de trabajos
Caché remota
Gestión de sesiones
Transmisión en vivo
Mesh
Sistema
Herramientas MCP (grub-crawl.py)
El puente MCP expone todas las capacidades a cualquier host compatible con MCP:
Módulos internos
Núcleo del agente (app/agent/)
Adaptadores de proveedor (app/agent/providers/)
Compuertas de políticas (app/policy/)
Observabilidad (app/observability/)
Capa de API
Anti-Detección (app/)
| Archivo | Propósito | Estado |
|---|
stealth.py | Parches playwright-stealth, bloqueo de dominios de rastreadores | Completado |
proxy.py | Resolución de proxy por solicitud con respaldo de variables de entorno | Completado |
Mesh (app/mesh/)
Infraestructura
Máquina de estados del 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
Condiciones de detención aplicadas en cada iteración:
- `max_steps` (por defecto: 12)
- `max_wall_time` (por defecto: 90s)
- `max_failures` (por defecto: 3)
- `no_op_loop` (3 respuestas vacías consecutivas)
- `policy_denied` (dominio/herramienta bloqueados)
- `completed` (el agente responde con texto)
## Anti-Detección
Tres capas de anti-detección que se apilan entre sí. La prevención detiene los bloqueos antes de que ocurran. Ghost Protocol se encarga de ellos después.
### Motor Camoufox
Navegador anti-detección conectable con suplantación de huellas digitales a nivel de C++. Sin trucos manuales de user-agent: Camoufox genera huellas digitales realistas por contexto a nivel del navegador, incluyendo canvas, WebGL, fuentes y propiedades del navigator.```bash
# Switch engine (default: chromium)
BROWSER_ENGINE=camoufox
Proxy por solicitud
Enruta el tráfico de rastreo a través de pools de proxies residenciales, de centro de datos o personalizados. Anulación por solicitud con valores predeterminados basados en variables de entorno. Configuración de proxy totalmente compatible 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"
}
}
}'
### Modo sigiloso
Parches opcionales (opt-in) de `playwright-stealth` para Chromium (se omiten para Camoufox, donde están integrados). Bloquea más de 20 dominios de seguimiento/análisis (Google Analytics, DataDome, PerimeterX, etc.) para reducir la superficie de huella digital.```bash
STEALTH_ENABLED=true
BLOCK_TRACKING_DOMAINS=true
Protocolo fantasma
Cuando un resultado de rastreo indica un bloqueo anti-bot (desafío de Cloudflare, CAPTCHA,
shell vacío de SPA), el agente puede cambiar al modo camuflaje:
- Tomar una captura de pantalla de página completa mediante Playwright
- Enviar la imagen a un LLM con capacidad de visión (Claude Sonnet o GPT-4o)
- Extraer el contenido de los píxeles renderizados
- Devolver el texto extraído con
render_mode: "ghost" en el trace
Esto evita por completo la detección anti-bot basada en DOM.
Requiere AGENT_GHOST_ENABLED=true. Se activa automáticamente en bloqueos detectados cuando AGENT_GHOST_AUTO_TRIGGER=true.
Malla
Agentes que hablan con agentes. Cada instancia de Grub es a la vez un worker y un coordinador. El nodo local descarga trabajo a la nube, la nube delega en el local. Las llamadas a herramientas cruzan la red de forma transparente.```
Node A (local) Node B (cloud)
┌─────────────┐ ┌─────────────┐
│ AgentEngine │ │ AgentEngine │
│ ↓ │ │ ↓ │
│ MeshDispatcher ──── HTTP ────→ MeshDispatcher │
│ ↓ │ │ ↓ │
│ Dispatcher │ │ Dispatcher │
│ ↓ │ │ ↓ │
│ ToolRegistry │ │ ToolRegistry │
└─────────────┘ └─────────────┘
↕ heartbeat (15s) ↕
└────────────────────────────────┘
**Cómo funciona:**
- **Descubrimiento** — los nodos se unen mediante una lista de pares semilla, luego se comunican (gossip) a 1 salto para conocer a otros
- **Heartbeat** — cada 15 s, los nodos intercambian métricas de carga. 3 fallos = no saludable. 2 min = eliminado
- **Enrutamiento** — MeshDispatcher puntúa todos los nodos por carga, localidad y afinidad, y luego enruta las llamadas de herramientas al mejor nodo
- **Máximo 1 salto** — solo nodo A → B, nunca A → B → C. Evita bucles de enrutamiento
- **Respaldo local** — si la ejecución remota falla, se recurre al Dispatcher local
- **Autenticación HMAC** — todo el tráfico del mesh está firmado con un secreto compartido (SHA-256, TTL de 60 s)
### Ejecutar un Mesh de 2 Nodos Localmente```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
Conectar local 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
### Configuración manual```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
Cuando mesh está deshabilitado (MESH_ENABLED=false, el valor por defecto), Grub funciona como un rastreador normal de un solo nodo sin sobrecarga de mesh.
Transmisión en vivo
Observa el rastreador trabajar en tiempo real. Un grupo persistente de instancias de Chromium activas transmite fotogramas de la ventana gráfica a través de WebSocket o MJPEG.
WebSocket — conéctate y envía comandos interactivos:```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** — colócalo en una etiqueta ``, video al instante:```html
<img src="http://localhost:6792/stream/my-session/mjpeg?url=https://example.com" />
Requiere BROWSER_STREAM_ENABLED=true. Cada instancia de Chromium utiliza ~150-300MB de RAM.
Inicio Rápido
Desarrollo Local```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
### Habilitar Modo Agente B```bash
# Add to .env
AGENT_ENABLED=true
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
AGENT_PROVIDER=anthropic
Enviar una tarea de 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-detección (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"}'
Transmisión en vivo del navegador```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"
## Configuración
### Servidor
- `HOST` (por defecto: 0.0.0.0)
- `PORT` (por defecto: 6792)
- `DEBUG` (por defecto: false)
### Almacenamiento
- `STORAGE_PATH` (por defecto: ./storage)
- `RUNNING_IN_CLOUD` (por defecto: false)
- `GCS_BUCKET_NAME`
- `GOOGLE_CLOUD_PROJECT`
### Autenticación
- `DISABLE_AUTH` (por defecto: false)
- `GNOSIS_AUTH_URL` (por defecto: http://gnosis-auth:5000)
### Motor del navegador
- `BROWSER_ENGINE` — chromium | camoufox (por defecto: chromium)
### Rastreo
- `MAX_CONCURRENT_CRAWLS` (por defecto: 5)
- `CRAWL_TIMEOUT` (por defecto: 30)
- `ENABLE_JAVASCRIPT` (por defecto: true)
- `ENABLE_SCREENSHOTS` (por defecto: false)
### Proxy
- `PROXY_SERVER` — URL del proxy (p. ej. http://proxy:10001)
- `PROXY_USERNAME`
- `PROXY_PASSWORD`
- `PROXY_BYPASS` — lista de exclusiones separada por comas
### Sigilo
- `STEALTH_ENABLED` (por defecto: false) — parches de playwright-stealth
- `BLOCK_TRACKING_DOMAINS` (por defecto: false) — bloquear solicitudes de analítica/seguimiento
### Agente (Modo B)
- `AGENT_ENABLED` (por defecto: false)
- `AGENT_MAX_STEPS` (por defecto: 12)
- `AGENT_MAX_WALL_TIME_MS` (por defecto: 90000)
- `AGENT_MAX_FAILURES` (por defecto: 3)
- `AGENT_ALLOWED_TOOLS` — lista de permitidos separada por comas
- `AGENT_ALLOWED_DOMAINS` — lista de permitidos separada por comas
- `AGENT_BLOCK_PRIVATE_RANGES` (por defecto: true)
- `AGENT_REDACT_SECRETS` (por defecto: true)
### Proveedores de LLM
- `AGENT_PROVIDER` — openai | anthropic | ollama (por defecto: openai)
- `OPENAI_API_KEY`
- `OPENAI_MODEL` (por defecto: gpt-4.1-mini)
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_MODEL` (por defecto: claude-3-5-sonnet-latest)
- `OLLAMA_BASE_URL` (por defecto: http://localhost:11434)
- `OLLAMA_MODEL` (por defecto: llama3.1:8b-instruct)
### Protocolo Ghost
- `AGENT_GHOST_ENABLED` (por defecto: false)
- `AGENT_GHOST_AUTO_TRIGGER` (por defecto: true)
- `AGENT_GHOST_VISION_PROVIDER` — hereda de AGENT_PROVIDER
- `AGENT_GHOST_MAX_IMAGE_WIDTH` (por defecto: 1280)
### Mesh
- `MESH_ENABLED` (por defecto: false) — interruptor maestro
- `MESH_PEERS` — URLs de pares semilla separadas por comas
- `MESH_NODE_NAME` — nombre legible por humanos (por defecto: hostname)
- `MESH_SECRET` — secreto HMAC compartido para autenticación entre nodos
- `MESH_ADVERTISE_URL` — URL que los pares usan para alcanzar este nodo
- `MESH_PREFER_LOCAL` (por defecto: true) — sesgo hacia la ejecución local
- `MESH_HEARTBEAT_INTERVAL_S` (por defecto: 15)
- `MESH_PEER_TIMEOUT_S` (por defecto: 45) — marcar como no disponible después de este tiempo
- `MESH_PEER_REMOVE_S` (por defecto: 120) — eliminar de la tabla de pares después de este tiempo
- `MESH_REMOTE_TIMEOUT_MS` (por defecto: 35000) — tiempo de espera para llamadas remotas de herramientas
### Transmisión en vivo
- `BROWSER_POOL_SIZE` (por defecto: 1)
- `BROWSER_STREAM_ENABLED` (por defecto: false)
- `BROWSER_STREAM_QUALITY` (por defecto: 25) — calidad JPEG 1-100
- `BROWSER_STREAM_MAX_WIDTH` (por defecto: 854)
- `BROWSER_STREAM_MAX_LEASE_SECONDS` (por defecto: 300)
## Contrato de respuesta
`POST /api/markdown` devuelve:
`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`
### Calidad del contenido
- `blocked` — anti-bot/captcha/reto
- `empty` — señal muy baja
- `minimal` — páginas escasas/de error
- `sufficient` — utilizable para resumir
No resumir a menos que `content_quality == "sufficient"`.
### Defensa contra inyección de prompts
- `quarantined=true` significa que el extractor detectó texto similar a instrucciones en el contenido extraído que no estaba presente en el texto renderizado visible de la página (común en el abuso de `.sr-only`/contenido visualmente oculto).
- Cuando está en cuarentena, `content_quality` se degrada a `minimal`, `policy_flags` incluye `hidden_text_suspected` y `quarantined`, y las salidas `content`/`markdown` se vacían (cierre ante fallos).
### Formato de error```json
{"error": "http_error|validation_error|internal_error", "status": 400, "details": {}}
Pruebas comparativas
Arena de combate — pruebas comparativas cara a cara contra Crawl4AI, Firecrawl (autoalojado) y Scrapy. Todas las pruebas se ejecutan en la misma máquina, las mismas URLs, las mismas condiciones. Grub se ejecuta primero como referencia; los adaptadores restantes se ejecutan en orden aleatorio con un retraso de 10s entre cada uno para evitar sesgos por limitación de velocidad.
Velocidad de una sola URL (ms, menor es mejor)
Grub gana en 4/5 carreras de velocidad de una sola URL. La conversión a Markdown se ejecuta en 0-21ms mediante el motor nativo en Rust (grub_md).
Desglose de fases de Grub (ms en servidor)
La navegación domina; la conversión a Markdown es submilisegundo en la mayoría de las páginas gracias al motor Rust.
Rendimiento por lotes (ms, menor es mejor)
Grub gana en 2 de 3 tamaños de lote. Coste por URL: 163-312ms (Grub) frente a 255-477ms (otros).
Cómo ejecutar```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
## Estado de Desarrollo
### Fase 1: Infraestructura Principal ✅
### Fase 2: Rastreo ✅
### Fase 3: Módulo de Agente ✅
- [x] Núcleo del agente — máquina de estados, tipos, errores (W1)
- [x] Contrato unificado de herramientas — despachador con timeout/reintento (W2)
- [x] Compuertas de política — lista de permitidos de dominios, denegación de rangos privados, redacción (W3)
- [x] Observabilidad — EventBus, TraceCollector, persistencia de RunSummary (W4)
- [x] Integración de API — `/api/agent/run`, `/api/agent/status`, JobType.AGENT_RUN (W5)
- [x] Adaptadores de proveedor — OpenAI, Anthropic, Ollama con respaldo (W6)
- [x] Flags de configuración — ajustes de agente, proveedor, ghost y stream (W7)
### Fase 4: Protocolo Ghost ✅
- [x] Detección de activación del modo camuflaje (W8)
- [x] Pipeline de captura de pantalla (W8)
- [x] Extracción de visión mediante Claude/GPT-4o (W8)
- [x] Cadena de respaldo en el motor (W8)
- [x] Herramienta Ghost para llamadores externos (W8)
- [x] Herramienta MCP Ghost + endpoint REST (W8)
### Fase 5: Transmisión de Navegador en Vivo ✅
- [x] Pool de navegadores persistente con préstamo/devolución (W9)
- [x] Retransmisión de screencast CDP (W9)
- [x] Endpoint WebSocket con comandos interactivos (W9)
- [x] Stream de respaldo MJPEG (W9)
- [x] Endpoints de estado del stream + estado del pool (W9)
### Fase 5.5: Anti-Detección ✅
- [x] Motor de navegador anti-detección Camoufox (W10)
- [x] Proxy por solicitud con respaldo de variables de entorno (W10)
- [x] Parches de sigilo para Chromium (W10)
- [x] Bloqueo de dominios de rastreadores/analíticas (W10)
- [x] Corrección de detección de formato de visión Anthropic (W10)
### Fase 6: Coordinador Mesh ✅
- [x] Descubrimiento de pares con gossip (1 salto) (W11)
- [x] Autenticación entre nodos HMAC-SHA256 (W11)
- [x] Bucle de heartbeat con métricas de carga + reintento de seed (W11)
- [x] MeshDispatcher — enrutamiento transparente de herramientas entre nodos (W12)
- [x] Puntuación basada en carga con bonificación de localidad/afinidad (W12)
- [x] Scripts de despliegue — local, malla, Cloud Run (W12)
- [x] Topología de malla de 2 nodos en Docker Compose (W12)
- [x] Página de aterrizaje integrada (grub-site) (W12)
### Fase 7: Rendimiento + Endurecimiento
- [x] Motor Markdown en Rust (`grub_md`) — extensión nativa PyO3, conversión en sub-ms
- [x] Arena de combate — benchmarks automatizados vs Crawl4AI, Firecrawl, Scrapy
- [x] Suite de pruebas unitarias — 176 pruebas en todos los módulos
- [ ] Mejoras en el manejo de errores
- [ ] Monitoreo y alertas
Ver [MASTER_PLAN.md](https://github.com/deepbluedynamics/grubcrawler/blob/HEAD/MASTER_PLAN.md) para el plan completo de arquitectura.
## Licencia
Licencia del Proyecto Grub Crawler