为什么选择 Grub
我们整合了所有主流爬虫的功能——然后添加了它们都没有的东西。
自托管爬虫
云端 / 托管爬虫
只有 Grub 拥有 Ghost Protocol——当标准爬取失败时,自动基于视觉的回退机制会截取被阻止页面的屏幕截图并通过 LLM 提取内容。预防措施(Camoufox + 代理 + 隐身)处理 95% 的阻止。Ghost Protocol 处理其余部分。
API 端点
核心爬取
PDF URL 由 /api/crawl、/api/markdown 和 /api/batch 处理,无需浏览器:文本层按页提取(PyMuPDF),纯图像页面回退到配置的视觉提供商进行 OCR(本地默认:Ollama 配合 benhaotang/Nanonets-OCR-s;设置 AGENT_GHOST_VISION_PROVIDER=anthropic 或 openai 并提供密钥以改用托管模型)。OCR 处理的页面标记为 source: "ocr" 并附带模型名称,其标题下会有一个 <!-- ocr: <model> --> 标记,因此转录内容绝不会被误认为源文本。输出为 markdown,每页一个 ## Page N 章节;render_mode 报告 pdf_text、pdf_vision、pdf_mixed 或 pdf_empty。
智能体(模式 B)
作业管理
远程缓存
会话管理
实时流
网状网络
系统
MCP 工具(grub-crawl.py)
MCP 桥接将所有能力暴露给任何兼容 MCP 的主机:
服务本身注册这些 AHP 工具;AHP 全捕获(GET /{tool_name})和 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`(默认值:12)
- `max_wall_time`(默认值:90s)
- `max_failures`(默认值:3)
- `no_op_loop`(连续 3 次空响应)
- `policy_denied`(被阻止的工具/域名)
- `completed`(agent 以文本响应)
## 反检测
三层反检测机制叠加在一起。预防机制在封锁发生之前就将其阻止。Ghost Protocol 则在封锁发生后进行处理。
### Camoufox 引擎
可插拔的反检测浏览器,具备 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"
}
}
}'
### 隐身模式
针对 Chromium 的可选 `playwright-stealth` 补丁(Camoufox 已内置该功能,故跳过)。拦截 20 多个跟踪/分析域名(Google Analytics、DataDome、PerimeterX 等),以减少指纹暴露面。```bash
STEALTH_ENABLED=true
BLOCK_TRACKING_DOMAINS=true
Ghost Protocol
当爬取结果触发反机器人拦截信号(Cloudflare 挑战、CAPTCHA、空 SPA 外壳)时,代理可以切换到隐身模式:
- 通过 Playwright 截取整页截图
- 将图像发送给具备视觉能力的 LLM(Claude、GPT-4o,或 Ollama 视觉模型,如 Nanonets-OCR-s)
- 从渲染后的像素中提取内容
- 返回提取的文本,并在 trace 中标记
render_mode: "ghost"
这完全绕过了基于 DOM 的反机器人检测。
需要 AGENT_GHOST_ENABLED=true。当 AGENT_GHOST_AUTO_TRIGGER=true 时,在检测到拦截时自动触发。
针对纯图像页面的 PDF OCR 使用相同的视觉提供程序,但不需要启用 Ghost。
Mesh
代理与代理对话。每个 Grub 实例既是工作节点也是协调节点。本地节点卸载到云端,云端委托给本地。工具调用透明地跨线传输。```
Node A (local) Node B (cloud)
┌─────────────┐ ┌─────────────┐
│ AgentEngine │ │ AgentEngine │
│ ↓ │ │ ↓ │
│ MeshDispatcher ──── HTTP ────→ MeshDispatcher │
│ ↓ │ │ ↓ │
│ Dispatcher │ │ Dispatcher │
│ ↓ │ │ ↓ │
│ ToolRegistry │ │ ToolRegistry │
└─────────────┘ └─────────────┘
↕ heartbeat (15s) ↕
└────────────────────────────────┘
**工作原理:**
- **发现** — 节点通过种子对等节点列表加入,然后通过 gossip(1 跳)了解其他节点
- **心跳** — 每 15 秒,节点交换负载指标。错过 3 次 = 不健康。2 分钟 = 移除
- **路由** — MeshDispatcher 根据负载、局部性和亲和性对所有节点进行评分,然后将工具调用路由到最佳节点
- **最多 1 跳** — 仅节点 A → B,绝不 A → B → C。防止路由环路
- **本地回退** — 如果远程执行失败,则回退到本地 Dispatcher
- **HMAC 认证** — 所有 mesh 流量均使用共享密钥签名(SHA-256,60 秒 TTL)
### 在本地运行 2 节点 Mesh```bash
# Docker Compose (recommended)
./scripts/deploy.sh mesh # Linux/Mac
./scripts/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
./scripts/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 被禁用时(MESH_ENABLED=false,默认值),Grub 作为普通的单节点爬虫运行,没有任何 mesh 开销。
实时流
实时观看爬虫工作。一个持久化的热 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 内存。
仓库布局```
grubcrawler/
├── app/ # FastAPI service — crawler, agent, mesh, policy, observability
├── site/ # Embedded landing / dashboard / docs pages
├── tests/ # Pytest suites
├── combat/ # Head-to-head benchmarks vs Crawl4AI / Firecrawl / Scrapy
├── examples/ # Integration examples (e.g. shivvr demo)
├── grub_md/ # Native Rust markdown extraction engine (maturin)
├── scripts/ # Deploy scripts — deploy.sh, deploy.ps1
├── plan/ # Architecture & planning docs (MASTER_PLAN, SERVICE_REGISTRY, CUSTOMER_ID)
├── Dockerfile # Service image (Playwright + Camoufox + Rust)
├── docker-compose.yml # Single-node local deploy
├── docker-compose.mesh.yml # 2-node mesh deploy
├── requirements.txt
├── pytest.ini
├── mcp.json # MCP tool config
├── gnosis-crawl.py # Standalone CLI client
└── README.md / CLAUDE.md / DEVELOPER.md / RUNBOOK.md
从仓库根目录调用部署脚本:`./scripts/deploy.sh local`(bash)或
`./scripts/deploy.ps1 -Target local`(PowerShell)。`deploy.sh` 会自行解析
项目根目录,因此从任何目录运行都能正常工作。
## 快速开始
### 本地开发```bash
git clone <repo>
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 模式 B```bash
Add to .env
AGENT_ENABLED=true
OPENAI_API_KEY=sk-...
or
ANTHROPIC_API_KEY=sk-ant-...
AGENT_PROVIDER=anthropic
### 提交代理任务```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
./scripts/deploy.sh local # or ./scripts/deploy.ps1 -Target local
2-node mesh
./scripts/deploy.sh mesh # or ./scripts/deploy.ps1 -Target mesh
Cloud Run
./scripts/deploy.sh cloudrun v1.0.0 # or ./scripts/deploy.ps1 -Target cloudrun -Tag v1.0.0
Cloud Run + mesh (connect to local node)
./scripts/deploy.sh cloudrun v1.0.0 --mesh-peer http://your-ip:6792 --mesh-secret mykey
本地 OCR:`docker-compose.yml` 将视觉提供程序指向宿主机的 Ollama
(`http://host.docker.internal:11434`),并期望先拉取 OCR 模型:```bash
ollama pull benhaotang/Nanonets-OCR-s
没有它,仅含图像的 PDF 页面会返回 empty,并在日志中留下警告。设置
AGENT_GHOST_VISION_PROVIDER=anthropic 或 openai 并配上对应的密钥,即可改用托管模型。
反检测(Camoufox + 代理)```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(默认值:anthropic)
- `OPENAI_API_KEY`
- `OPENAI_MODEL`(默认值:gpt-4.1-mini)
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_MODEL`(默认值:claude-haiku-4-5-20251001)
- `OLLAMA_BASE_URL`(默认值:http://localhost:11434;docker-compose 设置为 http://host.docker.internal:11434)
- `OLLAMA_MODEL`(默认值:llama3.1:8b-instruct)— 文本/工具调用模型
- `OLLAMA_VISION_MODEL`(默认值:benhaotang/Nanonets-OCR-s:latest)— Ghost + PDF OCR
- `OLLAMA_API_KEY` — 托管 Ollama 的 bearer token;本地不设置
- `OLLAMA_KEEP_ALIVE`(默认值:5m)、`OLLAMA_NUM_CTX`(默认值:8192)、`OLLAMA_VISION_TIMEOUT_S`(默认值:180)
### Ghost 协议
- `AGENT_GHOST_ENABLED`(默认值:false)
- `AGENT_GHOST_AUTO_TRIGGER`(默认值:true)
- `AGENT_GHOST_VISION_PROVIDER` — 继承自 AGENT_PROVIDER(docker-compose 设置为 ollama)
- `AGENT_GHOST_MAX_IMAGE_WIDTH`(默认值:1280)
### PDF 提取
- `PDF_ENABLED`(默认值:true)
- `PDF_MAX_BYTES`(默认值:52428800)、`PDF_MAX_PAGES`(默认值:300)
- `PDF_MIN_TEXT_CHARS`(默认值:40)— 低于此值时,页面被视为纯图像并进入 OCR
- `PDF_RENDER_DPI`(默认值:110)、`PDF_MAX_IMAGE_SIDE`(默认值:1280)
- `PDF_VISION_FALLBACK`(默认值:true)、`PDF_VISION_MAX_PAGES`(默认值:20)、`PDF_VISION_CONCURRENCY`(默认值:1)
每个变量的描述见 [DEVELOPER.md → Environment Variables](https://github.com/deepbluedynamics/grubcrawler/blob/main/DEVELOPER.md#environment-variables)。
### Mesh
- `MESH_ENABLED`(默认值:false)— 主开关
- `MESH_PEERS` — 逗号分隔的种子节点 URL
- `MESH_NODE_NAME` — 人类可读名称(默认值:hostname)
- `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` — 反机器人/验证码/挑战
- `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": {}}
基准测试
竞技场 — 与 Crawl4AI、Firecrawl(自托管)和 Scrapy 的正面基准测试。所有测试在同一台机器、相同 URL、相同条件下运行。Grub 作为基线首先运行,其余适配器以随机顺序运行,每个之间间隔 10 秒,以防止速率限制偏差。
单 URL 速度(毫秒,越低越好)
Grub 在 5 场单 URL 速度竞赛中赢得 4 场。Markdown 转换通过原生 Rust 引擎(grub_md)运行,耗时 0-21 毫秒。
Grub 阶段分解(服务器端毫秒)
导航占主导;得益于 Rust 引擎,大多数页面上的 markdown 转换耗时不到一毫秒。
批量吞吐量(毫秒,越低越好)
Grub 在 3 种批量大小中赢得 2 种。每个 URL 的成本:163-312 毫秒(Grub)对比 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
## 开发状态
### 阶段 1:核心基础设施 ✅
### 阶段 2:爬取 ✅
### 阶段 3:Agent 模块 ✅
- [x] Agent 核心 — 状态机、类型、错误(W1)
- [x] 统一工具契约 — 带超时/重试的调度器(W2)
- [x] 策略门控 — 域名允许列表、私有范围拒绝、脱敏(W3)
- [x] 可观测性 — EventBus、TraceCollector、RunSummary 持久化(W4)
- [x] API 接线 — `/api/agent/run`、`/api/agent/status`、JobType.AGENT_RUN(W5)
- [x] 提供商适配器 — OpenAI、Anthropic、Ollama 及回退(W6)
- [x] 配置标志 — agent、provider、ghost、stream 设置(W7)
### 阶段 4:Ghost 协议 ✅
- [x] 隐身模式触发检测(W8)
- [x] 截图捕获流水线(W8)
- [x] 通过 Claude/GPT-4o 进行视觉提取(W8)
- [x] 引擎中的回退链(W8)
- [x] 供外部调用方使用的 Ghost 工具(W8)
- [x] Ghost MCP 工具 + REST 端点(W8)
### 阶段 5:实时浏览器流 ✅
- [x] 带租用/归还的持久浏览器池(W9)
- [x] CDP 屏幕广播中继(W9)
- [x] 支持交互式命令的 WebSocket 端点(W9)
- [x] MJPEG 回退流(W9)
- [x] 流状态 + 池状态端点(W9)
### 阶段 5.5:反检测 ✅
- [x] Camoufox 反检测浏览器引擎(W10)
- [x] 带环境变量回退的按请求代理(W10)
- [x] Chromium 的隐身补丁(W10)
- [x] 跟踪器/分析域名拦截(W10)
- [x] Anthropic 视觉格式检测修复(W10)
### 阶段 6:Mesh 协调器 ✅
- [x] 基于 gossip(1 跳)的对等节点发现(W11)
- [x] HMAC-SHA256 节点间认证(W11)
- [x] 带负载指标 + 种子重试的心跳循环(W11)
- [x] MeshDispatcher — 透明的跨节点工具路由(W12)
- [x] 带局部性/亲和性加成的基于负载的评分(W12)
- [x] 部署脚本 — 本地、mesh、Cloud Run(W12)
- [x] Docker Compose 双节点 mesh 拓扑(W12)
- [x] 内嵌落地页(grub-site)(W12)
### 阶段 7:性能 + 加固
- [x] Rust markdown 引擎(`grub_md`)— PyO3 原生扩展,亚毫秒级转换
- [x] 竞技场 — 与 Crawl4AI、Firecrawl、Scrapy 的自动化基准测试
- [x] 单元测试套件 — 覆盖所有模块的 176 项测试
- [ ] 错误处理改进
- [ ] 监控与告警
完整架构方案见 [MASTER_PLAN.md](https://github.com/deepbluedynamics/grubcrawler/blob/main/plan/MASTER_PLAN.md)。
## 许可证
BSD 3-Clause License。Copyright (c) 2026, DeepBlue Dynamics, LLC。完整文本见 [LICENSE](https://github.com/deepbluedynamics/grubcrawler/blob/main/LICENSE)。