트렌딩 딥다이브 · 2026-07-19 · TrendShift 트렌딩 · ANY4AI

any4ai/anycrawl 딥다이브
"웹사이트를 LLM 먹이로" 바꾸는 오픈소스 크롤링 SaaS 백엔드

AnyCrawlURL 하나를 던지면 LLM이 바로 소화할 수 있는 마크다운·JSON으로 돌려주는 크롤링/스크래핑 API 서버다. 단일 페이지 스크래핑, 사이트 전체 크롤링, 검색엔진 결과(SERP) 수집, LLM 구조화 추출, 사이트맵 매핑까지 API 하나 뒤에 통합돼 있다. 겉으로는 "Firecrawl을 오픈소스로 따라한 스크래퍼"처럼 보이지만, 소스를 열어보면 Crawlee(엔진 실행) + BullMQ/Redis(분산 작업 큐) + Drizzle ORM(DB) + Vercel AI SDK(LLM 추출)를 엮은 제법 진지한 프로덕션급 백엔드다. 더 흥미로운 건 "봇 탐지를 실제로 뚫는 코드"가 그대로 공개돼 있다는 점이다 — Cloudflare Turnstile을 CDP(Chrome DevTools Protocol) 레벨에서 감지해 2captcha로 자동으로 풀고, 프록시를 base→stealth 단계로 자동 승급시키는 로직까지 전부 소스에 있다. 여기에 예약 크롤링(cron), 웹훅, 웹페이지 변경 감지(모니터), 사용량 과금(크레딧 원장), 사용자 커스텀 스크립트용 Node.js 샌드박스까지 갖춰서 — "스크래퍼 라이브러리"라기보다는 "스크래핑 전문 SaaS 한 채를 통째로 오픈소스로 공개한 것"에 가깝다.

(저장소: any4ai/anycrawl · TypeScript/MDX 혼합 · MIT · pnpm+Turborepo 모노레포 · @anycrawl/scrape 등 버전 1.0.0-beta.26 · 확인 시점 GitHub 3.3k stars · 소스는 2026-07-18 커밋 f2e0bc8 기준 클론해 직접 분석)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 주목받는가 — Firecrawl·Crawl4AI와 뭐가 다른가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 — 큐·엔진·안티봇·프록시
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 여기서 훔쳐올 설계
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

"URL을 받아 LLM이 먹기 좋은 데이터로 바꿔주는 API를, 큐 기반으로 안정적이게"
한 문장으로

AnyCrawl = "스크래핑 전문 콜센터" — 접수처(API)가 요청을 받아 우편함(Redis 큐)에 넣으면, 담당 직원(엔진 워커) 중 하나가 처리해 결과를 돌려준다

POST /v1/scrape에 URL 하나를 보내면 몇 초 안에 제목·마크다운·메타데이터·스크린샷 등을 JSON으로 돌려받는다. 내부적으로는 요청이 잡(Job)으로 변환돼 Redis 큐에 들어가고, 별도 프로세스(워커)가 이를 꺼내 cheerio(정적 HTML)·Playwright·Puppeteer(둘 다 실제 브라우저) 중 하나로 실행한 뒤, 완료되면 API가 그 결과를 폴링해 응답한다.

같은 파이프라인 위에 /v1/crawl(사이트 전체 순회), /v1/search(구글 등 SERP 수집), /v1/map(사이트맵 기반 URL 목록화), 그리고 json_options를 통한 LLM 구조화 추출까지 쌓여 있다.

README만 보면 "스크래핑 라이브러리"처럼 읽히지만, 실제 저장소 구조는 완전한 SaaS 백엔드의 모양을 하고 있다. API 키 발급·크레딧 차감(billingLedger), 웹훅 구독/재전송, cron 기반 예약 작업(scheduledTasks), 그리고 웹페이지 변경 감지(모니터)까지 DB 스키마 레벨에 1급 시민으로 존재한다. 즉 anycrawl.dev가 파는 호스팅 서비스와 자가 호스팅용 오픈소스가 같은 코드베이스다 — Docker Compose 한 줄로 로컬에 그대로 띄울 수 있다.

용어
LLM-ready 데이터
HTML을 그대로 주면 LLM은 태그·광고·내비게이션 잡음까지 같이 읽어야 해서 토큰 낭비가 심하다. "LLM-ready"란 본문만 남기고(only_main_content) 마크다운으로 변환하거나, 스키마를 던지면 곧바로 구조화 JSON으로 뽑아주는 형태를 말한다. AnyCrawl은 이 변환을 formats: ["markdown", "json", "screenshot", ...] 옵션 하나로 처리한다.

2왜 주목받는가 — Firecrawl·Crawl4AI와 뭐가 다른가

"LLM용 크롤러" 카테고리에서 오픈소스+분산 아키텍처 조합을 미는 자리

2026년 "LLM을 위한 웹 크롤러" 카테고리는 이미 붐비는 시장이다 — 호스팅 중심의 Firecrawl, 파이썬 진영의 Crawl4AI, 그리고 AnyCrawl이 나란히 언급된다. AnyCrawl의 GitHub 소개 문구조차 "Google/Bing/Baidu 등에서 구조화된 SERP를 추출한다"고 홍보하지만, 실제 오픈소스로 공개된 packages/search를 열어보면 직접 구현된 건 Google(cheerio로 google.com/search HTML을 직접 파싱)과 SearXNG(자체 호스팅 메타서치, 운영자가 붙여둔 엔진에 따라 Bing·DuckDuckGo 등을 간접 커버) 클라이언트뿐이고, Bing·Baidu 자체 파서는 없다 — 대신 ACEngine.ts라는 호스팅형 멀티 엔진 백엔드로 위임하는 얇은 HTTP 클라이언트가 있다. README를 그대로 믿지 않고 소스로 확인해야 하는 대표적인 사례다.

기존 도구와의 결정적 차이

FirecrawlCrawl4AIAnyCrawl
언어/런타임Node/TS (호스팅 중심)Python (라이브러리)Node/TS, pnpm+Turborepo 모노레포
실행 구조API 서버 + 워커단일 프로세스 async 라이브러리API(Express) + 엔진별 분산 큐/워커(BullMQ+Redis)
엔진 선택내부 브라우저 풀Playwright 중심cheerio·Playwright·Puppeteer 3종 + engine:"auto" 자동판별
안티봇호스팅 쪽 블랙박스기본 스텔스 플러그인Cloudflare Turnstile CDP 솔버 + 2captcha 연동이 소스로 공개
부가 기능Extract·Search(호스팅)주로 라이브러리 단위예약 작업·웹훅·변경 감지 모니터·크레딧 원장이 DB 스키마로 내장
자가 호스팅오픈소스 버전 별도 관리pip install 하나로 시작Docker Compose 한 줄, SQLite 기본값이라 DB 서버 불필요
비유

Crawl4AI가 "혼자 다 하는 팔방미인 프리랜서"라면, AnyCrawl은 "부서가 나뉜 회사"다. 프리랜서는 한 프로세스 안에서 요청부터 렌더링까지 순서대로 처리하지만, 회사는 접수팀(API)·현장팀(엔진 워커)·경리팀(크레딧/DB)·감시팀(모니터)이 따로 있고 우편함(Redis 큐)으로 업무를 주고받는다. 느리게 보일 수 있지만, 현장팀 중 한 명이 쓰러져도(브라우저 크래시) 회사 전체가 멈추지 않는다.

주목 포인트 4가지

① 엔진 3종 + 자동판별. engine: "auto"로 보내면 서버가 먼저 가벼운 HTTP GET으로 HTML을 5초 안에 받아 "JS가 꼭 필요한가"를 채점한 뒤 cheerio/Playwright 중 하나로 라우팅한다(4장에서 코드로 확인). README에는 나오지 않는, 소스에만 있는 기능이다.

② 진짜로 동작하는 안티봇 대응. Cloudflare Turnstile을 CDP로 감지하고 2captcha로 풀며, 성공한 cf_clearance 쿠키를 캐싱해 재사용한다. 프록시도 "일반(base)"과 "스텔스(stealth)" 두 단계로 나눠 자동/수동 승급시킨다.

③ "스크래퍼"를 넘어선 제품 범위. 예약 작업(cron)·웹훅 구독·모니터(주기적으로 재방문해 변경분만 diff)·사용자 커스텀 스크립트를 위한 샌드박스까지 — Firecrawl이 최근 붙이기 시작한 기능들을 처음부터 DB 스키마 레벨에서 준비해 뒀다.

④ MCP 서버 지원. anycrawl-mcp라는 별도 패키지(npx -y anycrawl-mcp)와 호스팅 엔드포인트(mcp.anycrawl.dev/{API_KEY}/mcp)로 Claude Desktop·Cursor·VS Code에서 바로 크롤링 도구를 쓸 수 있다 — 단, 이 MCP 서버 자체의 소스코드는 이 모노레포 안에 없고 문서(apps/docs)만 존재한다는 점은 분석 중 확인한 사실이다.

3기술 스택 전체 지도

API 서버 · 엔진 워커 · AI 추출 · DB · 문서 사이트, 5덩어리 모노레포

저장소는 pnpm + Turborepo 모노레포다. 앱은 2개(apps/api, apps/docs), 패키지는 9개(packages/*)로 나뉘어 있고, TypeScript 소스만 221개 파일·테스트를 제외하고도 약 5.6만 줄에 달한다.

anycrawl (모노레포 루트) │ package.json · turbo.json · pnpm-workspace.yaml · Dockerfile(all-in-one) │ ├─ apps/ │ ├─ api/ ← Express 5 REST API (컨트롤러 8개: scrape/crawl/search/map/ │ │ scheduled-tasks/webhooks/monitors/files) │ └─ docs/ ← Next.js 15 + Fumadocs 문서 사이트, 12개 언어(ko 포함) │ ├─ packages/ │ ├─ scrape/ ★ 크롤러 엔진 코어 (77개 TS 파일, 이 저장소의 심장) │ ├─ search/ SERP 엔진 (Google · SearXNG · AC-Engine 클라이언트) │ ├─ ai/ LLM 추출/요약/OCR 에이전트 (Vercel AI SDK) │ ├─ db/ Drizzle ORM 스키마 (SQLite ⇄ PostgreSQL 듀얼) │ ├─ template-client/ 사용자 커스텀 스크립트용 Node vm 샌드박스 │ ├─ libs/ 공용 설정·zod 스키마·로그·프록시·크레딧 계산 │ └─ js-sdk/ 공식 JS/TS 클라이언트 SDK │ └─ docker/ all-in-one 이미지용 supervisord 설정

① 백엔드 API (apps/api)

레이어선택비고
런타임Node.js ≥20 + TypeScript(ESM)type: "module", tsx로 개발 서버 구동
웹 프레임워크Express 5미들웨어: Auth · CheckCredits · DeductCredits · Log
검증Zod요청 스키마 → 타입 → (docs에서) OpenAPI까지 한 소스로 생성
라우트scrape·crawl·search·map·scheduled-tasks·webhooks·monitors8개 컨트롤러, 총 3,300여 줄

② 크롤링 엔진 (packages/scrape) — 이 레포의 핵심

레이어선택역할
크롤링 프레임워크Crawlee 3.x (Apify)CheerioCrawler/PlaywrightCrawler/PuppeteerCrawler, 요청 큐, 세션 풀
브라우저 런타임CloakBrowser 0.3.30Playwright/Puppeteer 실행을 감싸는 안티디텍션 브라우저 레이어 (Node ≥20 필요)
정적 파서cheerio + html-to-textJS 없이 HTML을 그대로 파싱 — 가장 빠른 엔진
작업 큐BullMQ + Redis(ioredis)엔진별·작업유형별로 큐를 쪼갬(scrape-cheerio 등)
DB 클라이언트Drizzle ORM + better-sqlite3 / pg잡·크레딧·웹훅·모니터 등 16개 테이블
안티봇CDP Turnstile 솔버 + 2captcha SDKCloudflare Turnstile 자동 해결
스케줄링node-cron + cron-parser예약 작업의 반복 실행 계산
기타@apify/utilities · minimatch · nodemailerURL 패턴 매칭, 알림 메일 등

③ AI 추출 레이어 (packages/ai)

Vercel AI SDK(ai v5) 위에 generateObject·streamObject로 구조화 추출을 구현했다. @ai-sdk/openai·@ai-sdk/openai-compatible·@openrouter/ai-sdk-provider를 함께 등록해 두어, OpenAI뿐 아니라 OpenAI 호환 엔드포인트(Atlas Cloud의 DeepSeek 등)·OpenRouter 모델을 DEFAULT_LLM_MODEL=atlascloud/deepseek-v3 같은 환경변수 한 줄로 바꿔 낄 수 있다. 토큰 계산에는 tiktoken을 쓰고, 긴 페이지는 TextChunker로 분할한다.

④ 데이터 계층 (packages/db)

Drizzle ORM으로 SQLite(자가 호스팅 기본값, 서버 설치 불필요)와 PostgreSQL(docker-compose.pg.yml, 스케일용) 스키마를 나란히 관리한다. 테이블은 jobs·jobResults·billingLedger·templates·scheduledTasks·taskExecutions·webhookSubscriptions·webhookDeliveries·pageCache·mapCache·monitors·monitorSnapshots·monitorChanges 등 16개.

⑤ 인프라 / 배포

도구용도
Docker (엔진별 이미지 3종)Dockerfile.cheerio/.playwright/.puppeteer — 브라우저가 필요없는 cheerio 이미지는 훨씬 가볍다
루트 Dockerfile("All-in-One")redis-server + supervisord로 API·워커·Redis를 한 컨테이너에서 실행 (빠른 체험용)
Turborepo + pnpm workspace모노레포 빌드 캐시·태스크 그래프
Jest + ts-jest패키지별 단위 테스트(__tests__/ 다수)
k6scripts/k6.js — 부하 테스트 스크립트 내장
Fumadocs + zod-openapiZod 스키마에서 OpenAPI 문서를 자동 생성해 docs 사이트에 반영
용어
Crawlee
Apify가 만든 Node.js용 크롤링 프레임워크. HTTP 파서(Cheerio)와 브라우저 자동화(Playwright/Puppeteer)를 같은 API 모양(요청 큐 + requestHandler)으로 다룰 수 있게 추상화한다. AnyCrawl의 BaseEngine은 이 위에 얹힌 한 겹의 커스텀 레이어다.
용어
BullMQ
Redis를 백엔드로 쓰는 Node.js용 작업 큐 라이브러리. "큐에 넣기(Producer, API 서버)"와 "꺼내서 처리하기(Consumer, 워커)"를 분리해, API 서버가 죽어도 큐에 쌓인 작업은 안전하고, 워커를 늘리면 처리량이 수평으로 늘어난다.

4아키텍처 심화 — 큐·엔진·안티봇·프록시

요청 하나가 "완료"되기까지 거치는 5개의 관문

AnyCrawl의 설계를 이해하려면 요청 하나의 여정을 따라가 보는 게 가장 빠르다. POST /v1/scrape가 들어오면 무슨 일이 벌어지는가?

Client / SDK / MCP │ POST /v1/scrape { url, engine, options... } ▼ apps/api (Express 5, ScrapeController) │ 1) 캐시 확인 — S3 페이지 캐시에 유효한 결과가 있으면 즉시 반환 │ (캐시 히트여도 "합성 job"을 만들어 과금·웹훅 흐름은 그대로 유지) │ 2) engine:"auto"면 가벼운 HTTP GET 한 번으로 JS 필요 여부 채점 │ 3) QueueManager.addJob("scrape-<engine>", payload) ▼ Redis ── BullMQ 큐들: scrape-cheerio / scrape-playwright / scrape-puppeteer │ crawl-cheerio / crawl-playwright / crawl-puppeteer │ scheduler (cron 반복 작업) ▼ packages/scrape Worker.ts (큐 1개당 워커 프로세스, concurrency=50) │ EngineQueueManager → EngineFactoryRegistry → {Cheerio|Playwright|Puppeteer}Engine │ extends BaseEngine.createCommonHandlers(...) │ ├─ EngineConfigurator 뷰포트 고정 1920×1080 · CDP로 광고/이미지/폰트 차단 ├─ ChallengeOrchestrator → CloudflareChallengeHandler │ └─ CDPTurnstileSolver → 2captcha API → cf_clearance 쿠키 캐싱 ├─ ProxyConfiguration base ⇄ stealth 티어 자동 승급 + 도메인별 캐시 ├─ DataExtractor │ ├─ HTMLTransformer only_main_content 규칙으로 광고/네비 제거 │ ├─ html-to-markdown (+ MarkdownOCR: 이미지 → 비전 LLM 설명 삽입) │ └─ ScreenshotTransformer └─ (json_options 있으면) packages/ai → LLMExtract.generateObject ▼ @anycrawl/db (Drizzle: SQLite | PostgreSQL) jobs / jobResults / billingLedger / monitors / webhookDeliveries ... │ └─ Webhook 발송 (scrape.created→started→completed/cancelled)

(A) 왜 큐를 엔진별·작업유형별로 쪼갰나

EngineQueueManagercheerio·playwright·puppeteer 각각에 대해 독립된 BullMQ 큐 + 워커를 만든다. scrape-*crawl-*도 따로 나뉜다. 이렇게 쪼개는 이유는 배포 유연성 때문이다 — docker-compose.yml을 보면 scrape-cheerio·scrape-playwright·scrape-puppeteer가 각각 별도 컨테이너(각자 다른 Dockerfile.cheerio/.playwright/.puppeteer)로 뜬다. 브라우저가 필요없는 cheerio 컨테이너는 이미지가 훨씬 가볍고, 무거운 Playwright/Puppeteer 워커만 따로 스케일아웃할 수 있다.

// managers/Worker.ts — BullMQ Worker는 그냥 이름+핸들러+concurrency
new Worker(name, async (job) => { return await jobHandler(job); }, {
    connection: Utils.getInstance().getRedisConnection(),
    concurrency: 50,
});

(B) 엔진 팩토리 + 레지스트리 패턴

engines/EngineFactory.tsBaseEngineFactory 추상 클래스 하나로 3개 엔진 팩토리(cheerio/playwright/puppeteer)의 공통 로직(기본 옵션 병합, 프록시 설정 주입, launchContext 병합)을 통일한다. 각 구체 팩토리는 "어느 모듈에서 어느 클래스를 동적 import할지"만 다르다.

export class EngineFactoryRegistry {
    private static factories = new Map();
    static {
        this.register('cheerio', new CheerioEngineFactory());
        this.register('playwright', new PlaywrightEngineFactory());
        this.register('puppeteer', new PuppeteerEngineFactory());
    }
    static async createEngine(engineType, queue, options) {
        const factory = this.factories.get(engineType);
        if (!factory) throw new Error(`No factory registered for engine type: ${engineType}`);
        return factory.createEngine(queue, options);
    }
}

새 엔진(예: 자체 HTTP/2 클라이언트)을 추가하려면 BaseEngine을 상속하고 팩토리를 하나 더 등록하면 끝이다 — 클라이언트 코드(컨트롤러·워커)는 엔진 타입 문자열만 알면 된다.

(C) engine:"auto" — HTML을 5초만 보고 JS 필요 여부를 채점한다

utils/autoEngine.ts는 README에는 없는, 소스를 읽어야만 보이는 기능이다. 가벼운 HTTP GET으로 HTML을 받아 다음과 같은 점수를 매긴다.

// 강한 신호(+3): 비어있는 루트 div, 본문 텍스트가 100자 미만인데 HTML은 2000자 초과,
// noscript 안에 의미있는 폴백 콘텐츠가 있음
if (/id=["'](?:root|app|__next|__nuxt)["'][^>]*>\s*<\/div>/i.test(rawHtml)) score += 3;

// 중간 신호(+1~2): __NEXT_DATA__ · __NUXT__ · data-reactroot 같은 프레임워크 흔적,
// script 태그가 10개 넘는데 보이는 텍스트는 500자 미만

// 상쇄 신호(-1~-3): 본문 텍스트가 이미 1000자 넘게 존재하면 오히려 JS가 필요없다고 판단

const engine = analysis.jsRequired ? "playwright" : "cheerio";
cache.set(domain, { engine }); // 도메인 단위로 캐싱 — 같은 사이트는 다음부터 재판별 안 함

즉 "이 페이지가 React/Vue SPA 껍데기인가, 이미 서버에서 렌더링된 페이지인가"를 휴리스틱 점수로 판정해 불필요한 브라우저 실행을 피한다. 실무에서 그대로 가져다 쓸 수 있는 실용적인 알고리즘이다.

(D) 안티봇 파이프라인 — Cloudflare Turnstile을 CDP로 뚫기

ChallengePlugin 인터페이스(onPreNavigation 훅 하나)를 구현한 CloudflareChallengeHandler가 실제로 하는 일:

  1. 프록시 모드가 stealth일 때만 동작(일반 모드는 그냥 통과)
  2. 세션에 저장된 cf_clearance 쿠키가 있으면 페이지 컨텍스트에 먼저 주입해 재도전 자체를 생략
  3. 그래도 Turnstile 위젯이 뜨면 CDPTurnstileSolver가 CDP로 challenge 파라미터를 추출해 2captcha API에 풀이를 요청
  4. 풀이에 성공하면 그 결과 쿠키를 캐시에 저장해 같은 도메인의 다음 요청부터 재사용
영리한 디테일
Turnstile 통과 후에도 상태 코드를 한 번 더 검증한다

createCommonHandlersretrySolvedChallengeWithReload는 챌린지를 풀었다는 상태(challengeState.solved)만 믿지 않는다. 풀이 직후 응답이 여전히 403이면 페이지를 한 번 더 reload해 최종 상태 코드를 재확인한다. "챌린지 통과"와 "실제 콘텐츠 로드 성공"이 항상 같지 않다는 걸 실전에서 겪고 넣은 방어 코드로 보인다.

(E) 프록시 티어링 — base와 stealth를 자동으로 오간다

resolveProxyModeWithFallback은 요청의 proxy 값에 따라 "시도할 프록시 풀의 순서"를 배열의 배열로 반환한다.

if (proxyValue === 'auto') {
    // 1티어: 저렴한 base 프록시 먼저 시도, 실패하면 2티어: stealth로 자동 승급
    if (baseProxyUrls.length > 0) {
        return stealthProxyUrls.length > 0
            ? [baseProxyUrls, stealthProxyUrls]
            : [baseProxyUrls];
    }
}

stealth로 명시하면 반대로 스텔스 프록시를 먼저 쓰고 실패 시 base로 내려간다. 여기에 도메인 단위 캐시(ProxyCacheManager)를 얹어, 한 번 stealth가 필요하다고 판정된 도메인은 이후 요청에서 바로 stealth로 시작한다 — 매번 재시도-실패-승급을 반복하지 않는다.

(F) CDP 레벨 리소스 차단 — DOM이 그려지기 전에 광고를 끊는다

브라우저 엔진(Playwright/Puppeteer)은 페이지마다 CDP 세션을 열어 Network.setBlockedURLs로 광고 도메인을 차단하고, Fetch.enable로 이미지·미디어·폰트 요청을 가로챈다. 스크린샷 포맷이 필요한 요청이면 이미지는 통과시키고 미디어만 막는 식으로 요청 목적에 따라 차단 규칙을 동적으로 바꾼다. 이 모든 처리가 페이지가 실제로 리소스를 내려받기 전에 CDP 레벨에서 끝나므로, "다운로드 후 DOM에서 숨기기"보다 훨씬 적은 대역폭을 쓴다(BandwidthTracker가 실제 절감량을 잡별로 기록한다).

핵심 설계 패턴 정리

5디렉토리 구조 해부

625개 파일 중 어디부터 열어봐야 하나
packages/scrape/src/ ★ 이 레포의 90%가 여기(77개 TS 파일) ├─ Worker.ts 엔트리포인트 — BullMQ 워커 부팅, 실행 상태 기록 ├─ engines/ ← ① 먼저 읽어라: 엔진이 뭘 하는지 │ ├─ Base.ts 추상 베이스(75KB) — 공통 핸들러·상태코드 판정 │ ├─ EngineFactory.ts ★ 팩토리+레지스트리, 기본 launchContext │ ├─ Cheerio.ts / Playwright.ts / Puppeteer.ts Crawlee 크롤러 얇은 래퍼 │ └─ README.md 엔진 아키텍처 자체 문서 (직접 작성한 설계 노트) │ ├─ core/ ← ② 두 번째로 읽어라: 요청 하나의 처리 단계 │ ├─ EngineConfigurator.ts ★ 브라우저 훅(뷰포트·CDP 차단·타임아웃), 550줄+ │ ├─ DataExtractor.ts HTML→마크다운, 메타데이터, 폴백 로직 │ ├─ CloakBrowserLauncher.ts 안티디텍션 브라우저 런처 연결 │ ├─ MarkdownOCR.ts 마크다운 속 이미지에 비전 LLM 설명 삽입 │ └─ transformers/ HTMLTransformer(본문 추출 규칙) · Screenshot │ ├─ managers/ ← ③ 분산 처리의 실체 │ ├─ Queue.ts / EngineQueue.ts / Worker.ts BullMQ 큐·워커 관리 │ ├─ Proxy.ts ★ 프록시 티어링(37KB) — base/stealth/auto │ ├─ Scheduler.ts cron 예약 작업(83KB, 이 레포에서 가장 큰 파일) │ ├─ Cache.ts / ProxyCacheManager.ts S3 페이지 캐시, 도메인별 프록시 캐시 │ └─ Webhook.ts 이벤트 발송 + 재시도 │ ├─ challenges/ · solvers/ ← ④ 안티봇 — Cloudflare Turnstile │ ├─ ChallengePlugin.ts 플러그인 인터페이스 │ ├─ cloudflare/CloudflareChallengeHandler.ts │ └─ CDPTurnstileSolver.ts / TwoCaptcha.ts │ └─ utils/autoEngine.ts engine:"auto" 휴리스틱 판별기 packages/search/src/engines/ Google.ts · Searxng.ts · ACEngine.ts packages/ai/src/agents/ LLMExtract.ts(572줄) · LLMSummary.ts · LLMOCR.ts packages/db/src/db/schemas/ SQLite.ts ⇄ PostgreSQL.ts (16개 테이블, 거의 대칭) packages/template-client/src/ sandbox/(Node vm) · constants/security.ts(차단 패턴) apps/api/src/controllers/v1/ Scrape·Crawl·Search·Map·ScheduledTasks·Webhooks·Monitor apps/docs/ Next.js + Fumadocs, content/docs/general/*.mdx (12개 언어)
읽는 순서 추천

처음 여는 사람에게: packages/scrape/src/engines/README.md(저장소 저자가 직접 쓴 설계 노트) → engines/EngineFactory.ts + Base.tscreateCommonHandlers(요청 하나의 표준 처리 흐름) → apps/api/src/controllers/v1/ScrapeController.ts(API가 큐에 뭘 어떻게 넣는지) → managers/Proxy.tschallenges/cloudflare/(안티봇/프록시의 실제 구현) → packages/db/src/db/schemas/SQLite.ts(제품이 실제로 뭘 기록하는지 한눈에). 이 5곳만 봐도 저장소의 핵심 로직 대부분이 손에 잡힌다.

6학습 포인트 — 여기서 훔쳐올 설계

크롤러에 관심 없어도 배울 값어치가 있는 것들
패턴 1 · 플러거블 엔진 아키텍처

팩토리+레지스트리로 "실행 방식"을 갈아끼우기

cheerio·Playwright·Puppeteer는 API가 완전히 다른 라이브러리인데, BaseEngine이라는 공통 인터페이스(init()·run()·stop())로 통일하고 EngineFactoryRegistry가 문자열 키 하나로 알맞은 구현체를 골라준다. "실행 전략을 런타임에 문자열로 선택"해야 하는 모든 시스템(결제 PG사 여러 개, 알림 채널 여러 개 등)에 그대로 적용 가능한 패턴이다.

패턴 2 · 가벼운 신호로 무거운 작업을 회피하기

engine:"auto"의 HTML 채점 휴리스틱

"브라우저를 켤지 말지"를 판단하려고 실제로 브라우저를 켜보지 않는다. 대신 5초짜리 가벼운 HTTP 요청 하나로 HTML을 받아 __NEXT_DATA__ 같은 프레임워크 흔적, 텍스트/스크립트 비율 같은 신호에 가중치를 매겨 점수화한다. "비싼 리소스를 쓰기 전에 값싼 신호로 필터링"하는 감각은 캐시 설계·오토스케일링 어디에나 응용된다.

패턴 3 · 안티봇을 플러그인으로 추상화

ChallengePlugin 인터페이스 + CDP 기반 캡차 자동 해결

Cloudflare 대응 로직을 onPreNavigation 훅 하나짜리 인터페이스로 뽑아뒀기 때문에, 다른 챌린지(예: hCaptcha)가 생겨도 새 플러그인만 추가하면 된다. CDP 세션으로 캡차 파라미터를 추출해 외부 솔버(2captcha)에 위임하고, 성공 쿠키를 캐싱해 재사용하는 흐름은 "브라우저 자동화 + 외부 API 위임"을 결합하는 실전 사례다.

패턴 4 · 캐시 히트에도 파이프라인을 흉내낸다

일관성을 위한 "합성(synthetic) 잡 레코드"

ScrapeController는 캐시가 적중해도 실제로 크롤링한 것처럼 createJob·insertJobResult·completedJob을 호출하고 scrape.created→started→completed 웹훅을 그대로 발송한다. 캐시 경로와 정상 경로가 "이후 처리(과금·웹훅·통계)" 관점에서 동일한 모양이 되도록 맞춘 것 — 캐싱을 나중에 끼워 넣을 때 흔히 놓치는 부분을 미리 설계해 둔 사례다.

패턴 5 · 안전한 사용자 스크립트 실행

정적 검사 + Node vm 샌드박스 + 메서드 화이트리스트

template-client의 사용자 커스텀 스크립트는 3단계로 방어된다 — ① 실행 전 정규식으로 require·eval·process·__proto__ 등 위험 패턴을 사전 차단(DANGEROUS_PATTERNS), ② Node.js 내장 vm 모듈로 격리된 컨텍스트에서 실행, ③ 호스트가 제공하는 API도 allowedPageMethods 화이트리스트로 제한. "사용자가 로직을 넣게 해주되 시스템은 안전하게" 만드는 3중 방어 조합을 그대로 참고할 수 있다.

패턴 6 · 스키마 하나로 검증·타입·문서를 동시에

Zod 스키마 우선 설계

packages/libs/src/types/*Schema.ts의 Zod 스키마가 (1) API 요청 검증, (2) TypeScript 타입, (3) apps/docs의 OpenAPI 문서 생성까지 세 가지 역할을 동시에 한다. 스키마와 문서가 따로 놀아 어긋나는 흔한 문제를 "진실의 원천을 하나로" 만들어 원천 차단한 설계다.

7하드웨어 / 시스템 요구사항

Redis는 필수, 브라우저 엔진은 생각보다 무겁다

cheerio 엔진만 쓸 거라면 노트북 한 대로 충분하다. Playwright/Puppeteer 엔진을 실제로 돌리려면 Chromium 프로세스가 페이지마다(또는 컨텍스트마다) 뜨는 만큼 메모리 여유가 필요하다.

항목요구/권장
Node.js20 이상 필수(engines.node >=20) — CloakBrowser가 Node 20+를 요구
패키지 매니저pnpm 10.12.4(packageManager 고정), Turborepo로 태스크 오케스트레이션
Redis필수. BullMQ 큐가 전부 Redis 위에서 동작 — 없으면 아예 기동 불가
데이터베이스SQLite(기본값, 파일 하나로 자가 호스팅 부담 없음) 또는 PostgreSQL(docker-compose.pg.yml, 스케일용)
브라우저 바이너리Playwright/Puppeteer 엔진용 Chromium — Docker 이미지 빌드 시 cloakbrowser install·playwright install chromium으로 사전 설치
동시성엔진 워커 기본 concurrency: 50 — 리소스가 적은 환경에선 ANYCRAWL_MAX_CONCURRENCY로 낮추길 권장
선택 사항S3 호환 스토리지(페이지 캐시·스크린샷), 2captcha API 키(Turnstile 자동 해결), 프록시 URL(base/stealth)
OSDocker 이미지는 node:20-bookworm-slim(Linux) 기반. 로컬 개발은 macOS/Linux/Windows 모두 지원(플랫폼별 Chromium 실행 인자 분기 있음)
포트API 기본 8080, Redis 기본 6379

배포 형태는 두 가지다. ① All-in-One 단일 컨테이너(루트 Dockerfile) — supervisord가 Redis·API·워커를 한 컨테이너 안에서 관리해 빠르게 체험하기 좋다. ② docker-compose 다중 컨테이너 — API/Redis/엔진별 워커가 각자 컨테이너로 분리돼 실제 운영 스케일링에 맞다.

8직접 해볼 수 있는 실습 과제

난이도별로, 소스를 손으로 만지며 이해하는 순서

과제 1. docker compose로 띄우고 엔진 3종 비교 난이도 ★☆☆

docker compose up으로 redis·api·scrape-cheerio·scrape-playwright·scrape-puppeteer를 띄운다. 같은 URL(예: JS로 렌더링되는 SPA 사이트)에 enginecheerio/playwright/puppeteer로 바꿔가며 /v1/scrape를 호출해, 응답 시간과 markdown 내용의 완성도가 어떻게 다른지 직접 비교한다.

과제 2. engine:"auto"의 판단 로그 관찰 난이도 ★★☆

engine: "auto"로 정적 사이트와 SPA 사이트를 각각 요청해보고, 워커 로그에 찍히는 [AutoEngine] <domain> -> <engine> (score=... reasons=...) 메시지를 확인한다. utils/autoEngine.ts의 점수 조건 하나를 직접 바꿔보고(예: 임계값 3 → 5) 판정이 어떻게 달라지는지 실험한다.

과제 3. LLM 구조화 추출 붙여보기 난이도 ★★☆

ai.config.example.json을 참고해 OpenAI 호환 프로바이더(로컬 Ollama나 OpenRouter도 가능)를 연결한 뒤, json_options.schema에 원하는 JSON 스키마를 넣어 /v1/scrape를 호출한다. packages/ai/src/agents/LLMExtract.tsnormalizeSchema가 스키마를 어떻게 변형하는지 로그로 확인해본다.

과제 4. 나만의 리소스 차단 규칙 추가 난이도 ★★★

core/EngineConfigurator.tsresourceBlockingHook에 특정 트래커/애널리틱스 도메인을 추가로 차단하는 규칙을 넣고, BandwidthTracker 로그로 대역폭 절감량이 실제로 줄어드는지 비교 측정한다.

과제 5. 템플릿 샌드박스에 안전한 API 추가 난이도 ★★★★

packages/template-client/src/constants/security.tsDANGEROUS_PATTERNSsandbox/index.ts를 읽고, preNav 단계에서 쓸 수 있는 안전한 헬퍼 함수(예: 랜덤 대기 시간 계산기)를 하나 추가해본다. 추가한 API가 화이트리스트를 우회해 위험한 동작을 하지 않는지 스스로 점검하며, "안전한 확장 지점을 설계한다"는 감각을 익힌다.

9관련 기술 심화 학습 로드맵

이 저장소를 발판 삼아 넓혀갈 5주 코스

1주차 — Crawlee와 엔진 추상화

Crawlee 공식 문서로 CheerioCrawler/PlaywrightCrawler/PuppeteerCrawler의 요청 큐·세션 풀·재시도 모델을 익히고, packages/scrape/src/engines/Base.tscreateCommonHandlers를 줄 단위로 읽는다. "여러 엔진을 하나의 인터페이스로 감싸는 법"이 이번 주 목표.

2주차 — 분산 큐/워커 아키텍처

BullMQ 공식 문서로 Producer/Consumer 분리, 재시도·백오프, repeatable job(cron) 개념을 익히고, managers/Queue.ts·managers/Scheduler.ts를 대조하며 읽는다. Redis 자료구조(Sorted Set 기반 지연 큐)가 왜 큐 라이브러리에 적합한지도 함께 살펴본다.

3주차 — 안티봇과 프록시 전략

Cloudflare Turnstile의 동작 원리, CDP(Chrome DevTools Protocol)의 Network/Fetch 도메인, 2captcha 같은 캡차 해결 서비스의 API 흐름을 익힌다. challenges/managers/Proxy.ts를 함께 읽으며 "언제 어떤 방어가 필요한가"를 판단하는 감각을 기른다.

4주차 — LLM 추출 파이프라인

Vercel AI SDK의 generateObject/streamObject, JSON Schema ↔ Zod 변환, 비전 모델 기반 OCR을 packages/ai/src/agents/로 실습한다. 토큰 계산(tiktoken)과 긴 문서 청킹 전략(TextChunker)도 함께 본다.

5주차 — 샌드박스 보안과 스키마 우선 설계

Node.js vm 모듈의 격리 한계(진짜 보안 경계가 아니라는 점 포함)를 학습하고, template-client의 정적 검사+런타임 화이트리스트 조합을 분석한다. 마지막으로 Zod → OpenAPI 자동 생성(zod-openapi, fumadocs-openapi) 흐름을 apps/docs에서 확인하며 스키마 우선 API 설계를 정리한다.

10핵심 키워드 사전

이 문서에 나온 용어를 한 줄로
Crawlee
Apify의 Node.js 크롤링 프레임워크. Cheerio/Playwright/Puppeteer 크롤러를 같은 요청 큐 모델로 통일한다.
BullMQ
Redis 기반 작업 큐. AnyCrawl은 엔진별·작업유형별로 큐를 쪼개 배포 단위를 분리한다.
CloakBrowser
Playwright/Puppeteer 실행을 감싸는 안티디텍션 브라우저 런처. Node 20+ 필요, Docker 빌드 시 바이너리 사전 설치.
engine:"auto"
가벼운 HTTP GET으로 HTML을 채점해 cheerio/Playwright 중 하나를 자동 선택하는 기능. 도메인 단위로 판정을 캐싱한다.
CDP (Chrome DevTools Protocol)
브라우저를 외부에서 제어하는 프로토콜. AnyCrawl은 이를 이용해 캡차 파라미터를 읽거나 특정 리소스 요청을 사전 차단한다.
Turnstile / 2captcha
Cloudflare의 캡차 시스템과, 이를 자동으로 풀어주는 유료 API 서비스. AnyCrawl은 CDP로 감지 후 2captcha에 위임한다.
프록시 티어(base/stealth/auto)
일반 프록시(base)와 고급 안티디텍션 프록시(stealth)를 두고, 실패 시 자동 승급(auto)하거나 도메인별로 캐싱하는 전략.
only_main_content
헤더·푸터·사이드바·광고 등을 제거하고 본문만 남기는 옵션. HTMLTransformer.ts가 Firecrawl과 유사한 규칙 목록으로 구현.
generateObject (Vercel AI SDK)
LLM에게 JSON Schema를 주고 그 형태에 맞는 구조화 객체를 생성하게 하는 함수. AnyCrawl의 LLM 추출 기능의 핵심.
Drizzle ORM
TypeScript용 경량 ORM. AnyCrawl은 SQLite와 PostgreSQL 스키마를 대칭적으로 유지해 자가호스팅과 스케일 배포를 모두 지원한다.
템플릿 샌드박스
사용자가 제출한 사이트별 커스텀 스크립트를 Node.js vm 모듈 + 정규식 사전검사 + 메서드 화이트리스트로 안전하게 실행하는 구조.
모니터(Monitor)
특정 URL을 주기적으로 재스크랩해 텍스트/JSON 변경분만 골라내는 기능. cron 예약 작업 + diff 저장(monitorChanges)으로 구현.
MCP 서버
Claude Desktop·Cursor 등에서 AnyCrawl을 도구로 쓰게 해주는 Model Context Protocol 연동. 별도 패키지(anycrawl-mcp)로 배포되며 이 모노레포에는 문서만 있다.

11참고 링크

원본부터 배경 지식까지

프로젝트

핵심 소스(먼저 읽을 파일)

기반 기술