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` (기본값: 12)
- `max_wall_time` (기본값: 90초)
- `max_failures` (기본값: 3)
- `no_op_loop` (연속 3회 빈 응답)
- `policy_denied` (차단된 도구/도메인)
- `completed` (에이전트가 텍스트로 응답)
## 안티 탐지
함께 중첩되는 3계층의 안티 탐지 기능입니다. Prevention은 차단이 발생하기 전에 막아내고, 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 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-hop)으로 다른 노드를 학습합니다.
- **Heartbeat** — 15초마다 노드가 부하 지표를 교환합니다. 3회 누락 = 비정상. 2분 = 제거.
- **Routing** — MeshDispatcher는 부하, 지역성, 선호도에 따라 모든 노드에 점수를 매긴 다음, 도구 호출을 최적의 노드로 라우팅합니다.
- **1-hop max** — 노드 A → B만 가능하며, A → B → C는 불가능합니다. 라우팅 루프를 방지합니다.
- **Local fallback** — 원격 실행이 실패하면 로컬 Dispatcher로 폴백합니다.
- **HMAC auth** — 모든 메시 트래픽은 공유 비밀키로 서명됩니다(SHA-256, 60초 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 RAM을 사용합니다.
빠른 시작
로컬 개발```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
에이전트 작업 제출```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 + 프록시)```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)
### Ghost Protocol
- `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` — 사람이 읽을 수 있는 이름 (기본값: 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은 기준선(baseline)으로 먼저 실행되며, 나머지 어댑터는 무작위 순서로 10초 간격으로 실행되어 속도 제한(rate-limiting) 편향을 방지합니다.
단일 URL 속도 (ms, 낮을수록 좋음)
Grub은 단일 URL 속도 경주에서 5판 4승을 기록합니다. Markdown 변환은 네이티브 Rust 엔진(grub_md)을 통해 0-21ms로 실행됩니다.
Grub 단계별 분석 (서버 측 ms)
탐색(navigation)이 지배적입니다. markdown 변환은 대부분의 페이지에서 1밀리초 미만이며, 이는 Rust 엔진 덕분입니다.
배치 처리량 (ms, 낮을수록 좋음)
Grub은 배치 크기 3개 중 2개에서 승리합니다. URL당 비용: 163-312ms(Grub) 대 255-477ms(기타).
실행 방법```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단계: 에이전트 모듈 ✅
- [x] 에이전트 코어 — 상태 머신, 타입, 오류 (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단계: 고스트 프로토콜 ✅
- [x] 클록 모드 트리거 감지 (W8)
- [x] 스크린샷 캡처 파이프라인 (W8)
- [x] Claude/GPT-4o를 통한 비전 추출 (W8)
- [x] 엔진의 폴백 체인 (W8)
- [x] 외부 호출자를 위한 고스트 도구 (W8)
- [x] 고스트 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단계: 메시 코디네이터 ✅
- [x] gossip 기반 피어 발견 (1-hop) (W11)
- [x] HMAC-SHA256 노드 간 인증 (W11)
- [x] 로드 메트릭 + 시드 재시도를 포함한 하트비트 루프 (W11)
- [x] MeshDispatcher — 투명한 크로스-노드 도구 라우팅 (W12)
- [x] 지역성/선호도 보너스가 적용된 로드 기반 점수 (W12)
- [x] 배포 스크립트 — 로컬, 메시, Cloud Run (W12)
- [x] Docker Compose 2-노드 메시 토폴로지 (W12)
- [x] 임베디드 랜딩 페이지 (grub-site) (W12)
### 7단계: 성능 + 강화
- [x] Rust 마크다운 엔진 (`grub_md`) — PyO3 네이티브 확장, 서브-ms 변환
- [x] 컴뱃 아레나 — Crawl4AI, Firecrawl, Scrapy 대비 자동 벤치마크
- [x] 유닛 테스트 스위트 — 모든 모듈에 걸친 176개 테스트
- [ ] 오류 처리 개선
- [ ] 모니터링 및 알림
전체 아키텍처 계획은 [MASTER_PLAN.md](https://github.com/deepbluedynamics/grubcrawler/blob/HEAD/MASTER_PLAN.md)를 참조하세요.
## 라이선스
Grub Crawler 프로젝트 라이선스