为什么选择 Grub
我们整合了每个主要爬虫的功能——并添加了它们都不具备的功能。
自托管爬虫
云/托管爬虫
只有 Grub 具备 Ghost Protocol——当标准爬取失败时,自动进行基于视觉的回退:截取被阻挡页面的截图并通过 LLM 提取内容。预防措施(Camoufox + 代理 + 隐身)可处理 95% 的阻挡。Ghost Protocol 处理其余部分。
API 端点
核心爬取
代理(模式 B)
作业管理
远程缓存
会话管理
实时流
网格
系统
MCP 工具(grub-crawl.py)
MCP 桥接将所有能力暴露给任何兼容 MCP 的主机:
内部模块
代理核心(app/agent/)
提供商适配器(app/agent/providers/)
策略门控(app/policy/)
可观测性(app/observability/)
API 层
反检测(app/)
| 文件 | 用途 | 状态 |
|---|
stealth.py | playwright-stealth 补丁、跟踪域名拦截 | 完成 |
proxy.py | 每请求代理解析,带环境变量回退 | 完成 |
网格(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
每次迭代强制执行的停止条件:
- `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)
## 反检测
三层反检测机制叠加在一起。预防在阻止发生前就将其拦截。幽灵协议则处理之后的后续。
### 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 挑战、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) ↕
└────────────────────────────────┘
**工作原理:**
- **发现(Discovery)** — 节点通过种子节点列表加入,然后通过gossip(1跳)了解其他节点
- **心跳(Heartbeat)** — 每15秒,节点交换负载指标。3次未响应视为不健康。2分钟未响应则移除
- **路由(Routing)** — MeshDispatcher根据负载、地域亲和性对所有节点进行评分,然后将工具调用路由到最佳节点
- **最大1跳** — 仅节点A→B,不允许A→B→C。防止路由环路
- **本地回退** — 若远程执行失败,则回退到本地Dispatcher
- **HMAC认证** — 所有网格流量均使用共享密钥签名(SHA-256, 60s TTL)
### 在本地运行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_ENABLED=false,默认设置)时,Grub 作为一个普通的单节点爬虫运行,零网格开销。
实时直播
实时观看爬虫工作。一个持久的热 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-300MB 内存。
快速开始
本地开发```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
提交 Agent 任务```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)
### 幽灵协议
- `AGENT_GHOST_ENABLED`(默认值:false)
- `AGENT_GHOST_AUTO_TRIGGER`(默认值:true)
- `AGENT_GHOST_VISION_PROVIDER` — 继承自 AGENT_PROVIDER
- `AGENT_GHOST_MAX_IMAGE_WIDTH`(默认值:1280)
### 网格
- `MESH_ENABLED`(默认值:false)— 主开关
- `MESH_PEERS` — 逗号分隔的种子对等节点 URL
- `MESH_NODE_NAME` — 人类可读的名称(默认值:主机名)
- `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` — 反机器人/CAPTCHA/挑战
- `empty` — 极低信号
- `minimal` — 内容稀薄/错误页面
- `sufficient` — 可用于摘要
除非 `content_quality == "sufficient"`,否则不要进行摘要。
### 提示注入防御
- `quarantined=true` 表示提取器在提取的内容中检测到类似指令的文本,而这些文本在页面的可见渲染文本中没有出现(常见于 `.sr-only`/视觉隐藏滥用)。
- 当被隔离时,`content_quality` 降级为 `minimal`,`policy_flags` 包含 `hidden_text_suspected` 和 `quarantined`,并且 `content`/`markdown` 输出被清空(故障关闭)。
### 错误格式```json
{"error": "http_error|validation_error|internal_error", "status": 400, "details": {}}
Benchmarks
竞赛场 —— 与 Crawl4AI、Firecrawl(自托管)和 Scrapy 进行面对面基准测试。所有测试在同一台机器、相同的 URL、相同的条件下运行。Grub 首先作为基线运行,其余适配器以随机顺序运行,每个之间延迟 10 秒以防止限速偏差。
单 URL 速度(毫秒,越低越好)
Grub 在 5 场单 URL 速度赛中赢得 4 场。Markdown 转换通过原生 Rust 引擎(grub_md)在 0-21 毫秒内完成。
Grub 阶段分解(服务器端毫秒)
导航占主导地位;得益于 Rust 引擎,大多数页面上的 Markdown 转换时间低于 1 毫秒。
批量吞吐量(毫秒,越低越好)
Grub 在 3 个批次大小中赢得 2 个。每个 URL 成本:Grub 为 163-312 毫秒,其他为 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
## Development Status
### Phase 1: Core Infrastructure ✅
### Phase 2: Crawling ✅
### Phase 3: Agent Module ✅
- [x] Agent core — state machine, types, errors (W1)
- [x] Unified tool contract — dispatcher with timeout/retry (W2)
- [x] Policy gates — domain allowlist, private-range deny, redaction (W3)
- [x] Observability — EventBus, TraceCollector, RunSummary persistence (W4)
- [x] API wiring — `/api/agent/run`, `/api/agent/status`, JobType.AGENT_RUN (W5)
- [x] Provider adapters — OpenAI, Anthropic, Ollama with fallback (W6)
- [x] Config flags — agent, provider, ghost, stream settings (W7)
### Phase 4: Ghost Protocol ✅
- [x] Cloak-mode trigger detection (W8)
- [x] Screenshot capture pipeline (W8)
- [x] Vision extraction via Claude/GPT-4o (W8)
- [x] Fallback chain in engine (W8)
- [x] Ghost tool for external callers (W8)
- [x] Ghost MCP tool + REST endpoint (W8)
### Phase 5: Live Browser Stream ✅
- [x] Persistent browser pool with lease/return (W9)
- [x] CDP screencast relay (W9)
- [x] WebSocket endpoint with interactive commands (W9)
- [x] MJPEG fallback stream (W9)
- [x] Stream status + pool status endpoints (W9)
### Phase 5.5: Anti-Detection ✅
- [x] Camoufox anti-detect browser engine (W10)
- [x] Per-request proxy with env fallback (W10)
- [x] Stealth patches for Chromium (W10)
- [x] Tracker/analytics domain blocking (W10)
- [x] Anthropic vision format detection fix (W10)
### Phase 6: Mesh Coordinator ✅
- [x] Peer discovery with gossip (1-hop) (W11)
- [x] HMAC-SHA256 inter-node auth (W11)
- [x] Heartbeat loop with load metrics + seed retry (W11)
- [x] MeshDispatcher — transparent cross-node tool routing (W12)
- [x] Load-based scoring with locality/affinity bonus (W12)
- [x] Deploy scripts — local, mesh, Cloud Run (W12)
- [x] Docker Compose 2-node mesh topology (W12)
- [x] Embedded landing page (grub-site) (W12)
### Phase 7: Performance + Hardening
- [x] Rust markdown engine (`grub_md`) — PyO3 native extension, sub-ms conversion
- [x] Combat arena — automated benchmarks vs Crawl4AI, Firecrawl, Scrapy
- [x] Unit test suite — 176 tests across all modules
- [ ] Error handling improvements
- [ ] Monitoring and alerting
See [MASTER_PLAN.md](https://github.com/deepbluedynamics/grubcrawler/blob/main/MASTER_PLAN.md) for the full architecture plan.
## License
Grub Crawler Project License