Por que Grub
Integramos funcionalidades de todos os principais crawlers — e depois adicionamos o que nenhum deles tem.
Crawlers Auto-Hospedados
Crawlers Gerenciados / Nuvem
Apenas Grub tem Ghost Protocol — fallback automático baseado em visão que captura páginas bloqueadas e extrai conteúdo via LLM quando o crawling padrão falha. Prevenção (Camoufox + proxy + stealth) lida com 95% dos bloqueios. Ghost Protocol lida com o restante.
Endpoints da API
Crawling Principal
Agente (Modo B)
Gerenciamento de Jobs
Cache Remoto
Gerenciamento de Sessões
Stream ao Vivo
Malha
Sistema
Ferramentas MCP (grub-crawl.py)
A ponte MCP expõe todas as capacidades a qualquer host compatível com MCP:
Módulos Internos
Núcleo do Agente (app/agent/)
Adaptadores de Provedor (app/agent/providers/)
Portões de Política (app/policy/)
Observabilidade (app/observability/)
Camada de API
Anti-Detecção (app/)
| Arquivo | Propósito | Status |
|---|
stealth.py | Patches playwright-stealth, bloqueio de domínios de rastreadores | Concluído |
proxy.py | Resolução de proxy por requisição com fallback de env | Concluído |
Malha (app/mesh/)
Infraestrutura
Máquina de Estado do 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
Stop conditions enforced every iteration:
- `max_steps` (padrão: 12)
- `max_wall_time` (padrão: 90s)
- `max_failures` (padrão: 3)
- `no_op_loop` (3 respostas vazias consecutivas)
- `policy_denied` (ferramenta/domínio bloqueado)
- `completed` (agente responde com texto)
## Anti-Detecção
Três camadas de anti-detecção que se sobrepõem. A prevenção interrompe os bloqueios antes que ocorram. O Ghost Protocol lida com eles depois.
### Camoufox Engine
Navegador anti-detectável plugável com falsificação de impressão digital em nível C++. Sem truques manuais de user-agent — o Camoufox gera impressões digitais realistas por contexto no nível do navegador, incluindo canvas, WebGL, fontes e propriedades do navigator.```bash
# Switch engine (default: chromium)
BROWSER_ENGINE=camoufox
Proxy por Requisição
Direcione o tráfego de rastreamento através de pools de proxy residenciais, de datacenter ou personalizados. Substituição por requisição com padrões baseados em variáveis de ambiente. Configuração de proxy totalmente compatível com 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"
}
}
}'
### Stealth Mode
Patches opcionais do `playwright-stealth` para Chromium (ignorado para o Camoufox, onde já está integrado). Bloqueia mais de 20 domínios de rastreamento/análises (Google Analytics, DataDome, PerimeterX, etc.) para reduzir a superfície de fingerprint.```bash
STEALTH_ENABLED=true
BLOCK_TRACKING_DOMAINS=true
Protocolo Fantasma
Quando um resultado de rastreamento sinaliza um bloqueio antibot (desafio Cloudflare, CAPTCHA,
casca SPA vazia), o agente pode alternar para o modo camuflagem:
- Tirar uma captura de tela de página inteira via Playwright
- Enviar a imagem para um LLM com capacidade de visão (Claude Sonnet ou GPT-4o)
- Extrair conteúdo dos pixels renderizados
- Retornar o texto extraído com
render_mode: "ghost" no rastreamento
Isso contorna completamente a detecção antibot baseada em DOM.
Requer AGENT_GHOST_ENABLED=true. Ativa automaticamente em bloqueios detectados quando AGENT_GHOST_AUTO_TRIGGER=true.
Malha
Agentes conversando com agentes. Toda instância do Grub é tanto um trabalhador quanto um coordenador. O nó local descarrega para a nuvem, a nuvem delega para o local. Chamadas de ferramentas atravessam o fio de forma transparente.```
Node A (local) Node B (cloud)
┌─────────────┐ ┌─────────────┐
│ AgentEngine │ │ AgentEngine │
│ ↓ │ │ ↓ │
│ MeshDispatcher ──── HTTP ────→ MeshDispatcher │
│ ↓ │ │ ↓ │
│ Dispatcher │ │ Dispatcher │
│ ↓ │ │ ↓ │
│ ToolRegistry │ │ ToolRegistry │
└─────────────┘ └─────────────┘
↕ heartbeat (15s) ↕
└────────────────────────────────┘
**Como funciona:**
- **Descoberta** — nós entram via lista de pares semente, depois fazem gossip (1-salto) para aprender sobre outros
- **Heartbeat** — a cada 15s, os nós trocam métricas de carga. 3 perdidos = não saudável. 2 min = removido
- **Roteamento** — MeshDispatcher pontua todos os nós por carga, localidade e afinidade, então roteia chamadas de ferramenta para o melhor nó
- **Máx. 1-salto** — Nó A → B apenas, nunca A → B → C. Previne loops de roteamento
- **Fallback local** — se a execução remota falhar, recai para o Dispatcher local
- **Autenticação HMAC** — todo tráfego da malha é assinado com um segredo compartilhado (SHA-256, TTL de 60s)
### Executar uma Malha de 2 Nós 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 ao 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
### Configuração 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
Quando o mesh está desabilitado (MESH_ENABLED=false, o padrão), o Grub opera como um crawler normal de nó único com zero overhead de mesh.
Transmissão ao Vivo
Veja o crawler trabalhar em tempo real. Um pool persistente de instâncias quentes do Chromium transmite quadros da viewport via WebSocket ou MJPEG.
WebSocket — conecte e envie comandos interativos:```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** — coloque-o em uma tag ``, vídeo instantâneo:```html
<img src="http://localhost:6792/stream/my-session/mjpeg?url=https://example.com" />
Requer BROWSER_STREAM_ENABLED=true. Cada instância do Chromium usa ~150-300MB de RAM.
Início Rápido
Desenvolvimento 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
### Ativar Agent Mode B```bash
# Add to .env
AGENT_ENABLED=true
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
AGENT_PROVIDER=anthropic
Submeter uma Tarefa do 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-detecção (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"}'
Transmissão ao Vivo do 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"
## Configuração
### Servidor
- `HOST` (padrão: 0.0.0.0)
- `PORT` (padrão: 6792)
- `DEBUG` (padrão: false)
### Armazenamento
- `STORAGE_PATH` (padrão: ./storage)
- `RUNNING_IN_CLOUD` (padrão: false)
- `GCS_BUCKET_NAME`
- `GOOGLE_CLOUD_PROJECT`
### Autenticação
- `DISABLE_AUTH` (padrão: false)
- `GNOSIS_AUTH_URL` (padrão: http://gnosis-auth:5000)
### Motor de Navegador
- `BROWSER_ENGINE` — chromium | camoufox (padrão: chromium)
### Crawling
- `MAX_CONCURRENT_CRAWLS` (padrão: 5)
- `CRAWL_TIMEOUT` (padrão: 30)
- `ENABLE_JAVASCRIPT` (padrão: true)
- `ENABLE_SCREENSHOTS` (padrão: false)
### Proxy
- `PROXY_SERVER` — URL do proxy (ex: http://proxy:10001)
- `PROXY_USERNAME`
- `PROXY_PASSWORD`
- `PROXY_BYPASS` — lista de bypass separada por vírgulas
### Furtividade (Stealth)
- `STEALTH_ENABLED` (padrão: false) — patches de playwright-stealth
- `BLOCK_TRACKING_DOMAINS` (padrão: false) — bloqueia requisições de análise/rastreamento
### Agente (Modo B)
- `AGENT_ENABLED` (padrão: false)
- `AGENT_MAX_STEPS` (padrão: 12)
- `AGENT_MAX_WALL_TIME_MS` (padrão: 90000)
- `AGENT_MAX_FAILURES` (padrão: 3)
- `AGENT_ALLOWED_TOOLS` — lista de permissões separada por vírgulas
- `AGENT_ALLOWED_DOMAINS` — lista de permissões separada por vírgulas
- `AGENT_BLOCK_PRIVATE_RANGES` (padrão: true)
- `AGENT_REDACT_SECRETS` (padrão: true)
### Provedores LLM
- `AGENT_PROVIDER` — openai | anthropic | ollama (padrão: openai)
- `OPENAI_API_KEY`
- `OPENAI_MODEL` (padrão: gpt-4.1-mini)
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_MODEL` (padrão: claude-3-5-sonnet-latest)
- `OLLAMA_BASE_URL` (padrão: http://localhost:11434)
- `OLLAMA_MODEL` (padrão: llama3.1:8b-instruct)
### Protocolo Fantasma (Ghost Protocol)
- `AGENT_GHOST_ENABLED` (padrão: false)
- `AGENT_GHOST_AUTO_TRIGGER` (padrão: true)
- `AGENT_GHOST_VISION_PROVIDER` — herda de AGENT_PROVIDER
- `AGENT_GHOST_MAX_IMAGE_WIDTH` (padrão: 1280)
### Malha (Mesh)
- `MESH_ENABLED` (padrão: false) — chave mestre
- `MESH_PEERS` — URLs de peers seed separadas por vírgulas
- `MESH_NODE_NAME` — nome legível por humanos (padrão: hostname)
- `MESH_SECRET` — segredo HMAC compartilhado para autenticação entre nós
- `MESH_ADVERTISE_URL` — URL que os peers usam para alcançar este nó
- `MESH_PREFER_LOCAL` (padrão: true) — tendência para execução local
- `MESH_HEARTBEAT_INTERVAL_S` (padrão: 15)
- `MESH_PEER_TIMEOUT_S` (padrão: 45) — marcar como não saudável após isso
- `MESH_PEER_REMOVE_S` (padrão: 120) — remover da tabela de peers após isso
- `MESH_REMOTE_TIMEOUT_MS` (padrão: 35000) — timeout para chamadas de ferramentas remotas
### Transmissão ao Vivo (Live Stream)
- `BROWSER_POOL_SIZE` (padrão: 1)
- `BROWSER_STREAM_ENABLED` (padrão: false)
- `BROWSER_STREAM_QUALITY` (padrão: 25) — qualidade JPEG 1-100
- `BROWSER_STREAM_MAX_WIDTH` (padrão: 854)
- `BROWSER_STREAM_MAX_LEASE_SECONDS` (padrão: 300)
## Contrato de Resposta
`POST /api/markdown` retorna:
`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`
### Qualidade do Conteúdo
- `blocked` — anti-bot/captcha/desafio
- `empty` — sinal muito baixo
- `minimal` — páginas finas/de erro
- `sufficient` — utilizável para sumarização
Não sumarize a menos que `content_quality == "sufficient"`.
### Defesa contra Injeção de Prompt
- `quarantined=true` significa que o extrator detectou texto semelhante a instruções no conteúdo extraído que não estava presente no texto renderizado visível da página (comum em abusos de `.sr-only`/visualmente oculto).
- Quando em quarentena, `content_quality` é rebaixado para `minimal`, `policy_flags` inclui `hidden_text_suspected` e `quarantined`, e as saídas `content`/`markdown` são limpas (falha fechada).
### Formato de Erro```json
{"error": "http_error|validation_error|internal_error", "status": 400, "details": {}}
Benchmarks
Arena de combate — benchmarks diretos contra Crawl4AI, Firecrawl (auto-hospedado) e Scrapy. Todos os testes executados na mesma máquina, mesmas URLs, mesmas condições. Grub executa primeiro como linha de base, os adaptadores restantes em ordem aleatória com atraso de 10s entre cada um para evitar viés de limitação de taxa.
Velocidade de URL Única (ms, menor é melhor)
Grub vence 4/5 corridas de velocidade de URL única. Conversão Markdown executa em 0-21ms via mecanismo Rust nativo (grub_md).
Detalhamento de Fase do Grub (ms no servidor)
Navegação domina; conversão Markdown é sub-milissegundo na maioria das páginas graças ao mecanismo Rust.
Throughput de Lote (ms, menor é melhor)
Grub vence 2/3 tamanhos de lote. Custo por URL: 163-312ms (Grub) vs 255-477ms (outros).
Como Executar```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
## Status do Desenvolvimento
### Fase 1: Infraestrutura Principal ✅
### Fase 2: Rastreamento ✅
### Fase 3: Módulo Agente ✅
- [x] Núcleo do agente — máquina de estados, tipos, erros (W1)
- [x] Contrato unificado de ferramentas — dispatcher com timeout/retry (W2)
- [x] Portas de política — lista de permissões de domínios, negação de faixa privada, redação (W3)
- [x] Observabilidade — EventBus, TraceCollector, persistência de RunSummary (W4)
- [x] Conexão de API — `/api/agent/run`, `/api/agent/status`, JobType.AGENT_RUN (W5)
- [x] Adaptadores de provedores — OpenAI, Anthropic, Ollama com fallback (W6)
- [x] Flags de configuração — agente, provedor, ghost, configurações de stream (W7)
### Fase 4: Protocolo Ghost ✅
- [x] Detecção de gatilho do modo camuflagem (W8)
- [x] Pipeline de captura de tela (W8)
- [x] Extração de visão via Claude/GPT-4o (W8)
- [x] Cadeia de fallback no motor (W8)
- [x] Ferramenta Ghost para chamadores externos (W8)
- [x] Ferramenta Ghost MCP + endpoint REST (W8)
### Fase 5: Stream ao Vivo do Navegador ✅
- [x] Pool persistente de navegadores com lease/retorno (W9)
- [x] Relay de screencast CDP (W9)
- [x] Endpoint WebSocket com comandos interativos (W9)
- [x] Stream de fallback MJPEG (W9)
- [x] Endpoints de status de stream + status do pool (W9)
### Fase 5.5: Antideteção ✅
- [x] Motor de navegador antideteção Camoufox (W10)
- [x] Proxy por requisição com fallback de env (W10)
- [x] Patches de furtividade para Chromium (W10)
- [x] Bloqueio de domínios de rastreadores/análises (W10)
- [x] Correção de detecção de formato de visão Anthropic (W10)
### Fase 6: Coordenador de Malha ✅
- [x] Descoberta de pares com gossip (1-salto) (W11)
- [x] Autenticação entre nós HMAC-SHA256 (W11)
- [x] Loop de heartbeat com métricas de carga + retry de seed (W11)
- [x] MeshDispatcher — roteamento transparente de ferramentas entre nós (W12)
- [x] Pontuação baseada em carga com bônus de localidade/afinidade (W12)
- [x] Scripts de deploy — local, mesh, Cloud Run (W12)
- [x] Topologia de malha de 2 nós Docker Compose (W12)
- [x] Página de destino incorporada (grub-site) (W12)
### Fase 7: Desempenho + Fortalecimento
- [x] Motor Rust de markdown (`grub_md`) — extensão nativa PyO3, conversão sub-ms
- [x] Arena de combate — benchmarks automatizados vs Crawl4AI, Firecrawl, Scrapy
- [x] Suíte de testes unitários — 176 testes em todos os módulos
- [ ] Melhorias no tratamento de erros
- [ ] Monitoramento e alertas
Veja [MASTER_PLAN.md](https://github.com/deepbluedynamics/grubcrawler/blob/HEAD/MASTER_PLAN.md) para o plano completo da arquitetura.
## Licença
Licença do Projeto Grub Crawler