AI 에이전트를 위한 JavaScript가 많은 페이지 스크래핑 방법
요약
AI 에이전트가 JavaScript 기반의 SPA(Single Page Application) 페이지를 스크래핑할 때 발생하는 문제를 해결하는 방법을 제시합니다. 단순히 HTTP 요청만으로는 내용이 부족하므로, 저렴한 정적 추출(Web Extract)을 먼저 시도하고 결과가 빈약할 경우에만 헤드리스 브라우저 렌더링(Web Render)을 수행하여 정확도를 높이는 폴백 기능을 구축하는 가이드입니다.
핵심 포인트
- SPA 페이지는 일반 HTTP 요청으로 내용 전체를 가져오기 어렵습니다.
- 정적 추출(Extract) 후 결과가 빈약할 때만 헤드리스 브라우저 렌더링(Render)을 사용합니다.
- 이 폴백 방식은 비용 효율적이며, 실제 브라우저 환경을 시뮬레이션합니다.
- Web Extract와 Web Render 모두 $0.005의 저렴한 비용으로 호출 가능합니다.
AI 에이전트를 위한 JavaScript가 많은 페이지 스크래핑 방법
React나 Vue의 싱글 페이지 애플리케이션(SPA)에 일반적인 HTTP 페처를 사용하면 종종 껍데기만 받게 됩니다. 즉, 제목과 비어 있는 <div id="root">, 그리고 JavaScript 활성화를 요청하는 줄이 전부입니다. 이것을 LLM에게 전달하면 페이지가 비어 있다고 말하거나, 더 나쁜 경우 메모리에 저장된 정보로 답변하게 됩니다.
해결책은 모든 URL에 대해 헤드리스 브라우저를 실행하는 것이 아닙니다. 먼저 저렴하게 가져와서(fetch cheaply first) 결과가 빈약한지 확인하고, 그런 페이지에만 렌더링을 수행하는 것입니다. 이 가이드는 Pocket Agentic Portal의 AgentSearch 도구에서 TypeScript로 이러한 폴백(fallback) 기능을 구축합니다: 정적 HTML용 Web Extract와 브라우저가 필요한 페이지용 Web Render입니다. 각 호출은 Base 또는 Tempo의 x402를 통해 $0.005 USDC로 지불되며, 가입이나 API 키가 필요 없습니다.
클라이언트 렌더링 페이지가 비어 보이는 이유
서버 렌더링(server-rendered)된 페이지는 텍스트를 HTML에 담아 전송합니다. 반면, 클라이언트 렌더링(client-rendered)된 페이지는 로드 후에 텍스트를 구축하는 JavaScript를 전송합니다. Extract는 수신한 HTML을 읽어 마크다운으로 변환하므로, 클라이언트 렌더링된 페이지의 경우 비어 있는 템플릿을 충실하게 변환합니다.
Render는 해당 페이지를 헤드리스 Chromium에서 실행한 다음, 브라우저가 최종적으로 가지고 있는 내용(markdown, text, html, title, links 및 선택적 스크린샷)을 반환합니다. 이는 실제 브라우저이므로 JavaScript가 많은 페이지에 대한 도구입니다. 또한 Extract와 호출당 $0.005로 비용이 동일하기 때문에 폴백으로 작동합니다: 깨끗하게 추출되는 페이지는 두 번째 호출 비용($0.01)을 지불하지 않습니다.
1단계: 유료 호출 래퍼
이것은 AI 에이전트 웹 도구의 오류 처리(Error Handling for AI Agent Web Tools)에서 사용된 것과 동일한 래퍼를 간소화한 것입니다. payFetch는 AI 에이전트를 위한 종량제 API(Pay-per-call APIs for AI agents)의 x402 클라이언트이며, 지출 상한(spend cap)이 설치되어 있습니다.
// js-pages.ts
const PORTAL = "https://agent.pocket.network/v1";
declare const payFetch: typeof fetch; // from the x402 tutorial
...
2단계: 콘텐츠가 적거나 클라이언트 측에서 렌더링된 페이지 감지하기
Extract는
Render는 느린 애플리케이션을 위해 몇 가지 옵션을 제공합니다: wait_until (기본값은 domcontentloaded, 또는 load나 networkidle), wait_for_selector (CSS 선택자, 최대 1.5초 동안 대기), 그리고 wait_ms (0에서 1500 사이)를 사용할 수 있으며, 또한 image, media, font 또는 stylesheet 요청 로딩을 건너뛰는 block 옵션(기본값은 ["media", "font"])도 있습니다. 대기 시간을 길게 설정할수록 아래의 마감 시간(deadline)이 줄어들므로, 기본값을 사용하는 것이 좋습니다.
4단계: robots.txt 422 오류, 마감 시간 및 부분 콘텐츠 처리
세 가지 결과에 대해 각각 별도의 처리가 필요합니다.
robots.txt가 페이지를 차단하는 경우. Render는 robots.txt를 준수합니다 (RFC 9309, 사용자 에이전트 토큰 AgentSearchRender; 해당 브라우저는 Chrome 사용자 에이전트 문자열로 자신을 식별하며 끝은 AgentSearchRender/0.1 (+https://agentsearchhq.com/agents.md)입니다). 허용되지 않은 URL은 가져오기(fetch)가 발생하기 전에 거부됩니다. 직접 호출할 경우, Render는 HTTP 422 응답을 반환합니다:
{"error": {"code": "ROBOTS_DISALLOWED", "message": "robots.txt가 AgentSearchRender에 대해 이 URL을 허용하지 않습니다.", "retryable": false, "request_id": "…"}}
Pocket 포털을 통해 접근할 경우 이는 코드 UPSTREAM_REJECTED와 함께 HTTP 400으로 표시되며, 해당 호출은 x402로 청구되지 않습니다. 단순 차단(plain disallow)의 경우 응답 내용은 변하지 않으므로, 이를 "다른 출처를 사용하라"는 의미로 간주하고 사이트의 선택을 존중해야 합니다. (만약 사이트 자체의 robots.txt가 서버 오류로 응답했다면, Render 역시 거부하지만 이 경우에는 retryable: true로 표시합니다.)
렌더링이 마감 시간에 도달하는 경우. Render의 하드 데드라인은 요청이 도착한 시점부터 계산되어 4.2초입니다. 시간이 초과되면 여전히 HTTP 200 응답이며, error.code는 TARGET_DEADLINE으로 설정됩니다. 이 코드는 항상 retryable: true이며, markdown에는 이미 제시간에 렌더링된 텍스트가 포함되어 있을 수 있습니다. 해당 부분 페이지를 보존하고 모델에게 내용이 불완전할 수 있음을 알려주세요.
기타 웹사이트 오류. 이러한 오류는 HTTP 200 응답의 data.error 내부에 도착하며, 해당 호출은 성공적으로 전달된 것으로 간주됩니다. message가 아닌 code와 retryable을 기준으로 분기해야 합니다.
function portalResult(url: string, f: PortalFailure): ModelPage {
// robots.txt refusal: 400 UPSTREAM_REJECTED via the portal (not charged on x402), 422 ROBOTS_DISALLOWED direct
if (f.code === "UPSTREAM_REJECTED" || f.code === "ROBOTS_DISALLOWED") return { status: "skip", url, code: f.code };
...
retry_later는 '다른 차례에 이 URL을 시도해라'라는 의미이지, '지금 루프를 돌라'는 의미가 아닙니다. 전달되는 재시도는 각각 새로운 $0.005 호출입니다. Extract의 TARGET_TIMEOUT과 Render의 TARGET_BOT_WALL(봇 확인 또는 챌린지 페이지로, Render가 우회하는 것이 아니라 보고하므로 해당 소스는 건너뜁니다)를 포함한 전체 오류 코드 목록은 error-handling guide에 있습니다.
5단계: 비용 통제하기
폴백(fallback) 기능만으로도 대부분의 작업이 가능합니다. 정적 페이지는 호출 한 번, JS가 많은 페이지는 두 번의 호출이 필요합니다. 다음 습관 몇 가지를 더 지키면 루프를 예측 가능하게 유지할 수 있습니다:
- 코드에서 태스크당 호출 횟수 제한. 8회의 전달된 호출 예산은 모델이 무엇을 하든 $0.04입니다.
- 클라이언트 렌더링인 것을 아는 호스트에 대해서는 Extract 건너뛰기. 만약 특정 도메인이 지난번에 내용이 빈약했다면, 첫 번째 호출을 절약하기 위해 바로 Render를 사용하세요.
- 최종 URL로 중복 제거(Dedupe). 동일한
url로 리디렉션되는 두 개의 검색 결과는 하나의 페이지입니다. - 먼저 JSON 엔드포인트를 확인. 많은 SPA(Single Page Application)가 공개 JSON URL에서 콘텐츠를 로드합니다. 이미 그 주소를 알고 있다면, 해당 주소에 대한 단순 fetch만으로 충분합니다.
- 요청하는 형식 줄이기. 대부분의 에이전트 읽기에는
markdown하나만으로 충분합니다. 필요하지 않다면html과 스크린샷은 건너뛰세요.
결과로 얻은 markdown을 시스템 프롬프트의 일부가 아닌, 도구 결과(tool result)로 모델에 전달하세요. 렌더링되었든 아니든, 그것은 여전히 다른 사람의 텍스트입니다.
Claude, Cursor 또는 기타 MCP 클라이언트에서 사용하기
클라이언트를 직접 작성하는 대신 Pocket의 MCP 서버를 사용하면 자체 백엔드 없이 Base에서 x402를 지불할 수 있습니다:
{
"mcpServers": {
"pocket-network": {
...
금액은 USDC 원자 단위(6자리 소수점): 서버가 실행되는 동안 총 서명 가능한 100만 캡의 POCKET_MAX_TOTAL_ATOMIC과 한 번의 호출당 최대 5,000 캡의 POCKET_MAX_PER_CALL_ATOMIC을 사용합니다. 이 제한은 무언가를 서명하기 전에 확인됩니다. 오직 지출할 의사가 있는 금액만 보유한 지갑을 사용하세요. 모델에게도 동일한 규칙을 지침에 명시하세요: 먼저 Extract를 호출하고, 마크다운이 비어 있거나 JavaScript를 요청할 때만 Render를 호출하도록 합니다.
엔드포인트 (Endpoints)
- Web Extract:
POST https://agent.pocket.network/v1/agentsearch-web-extract-v1/v1/extract(포털 페이지) - Web Render:
POST https://agent.pocket.network/v1/agentsearch-web-render-v1/v1/render(포털 페이지) - Web Search:
POST https://agent.pocket.network/v1/agentsearch-web-search-v1/v1/search(포털 페이지)
세 가지 도구 중 어떤 것을 선택해야 하는지에 대한 일반적인 내용은 Search, Extract, Render: AI 에이전트를 위한 세 가지 웹 도구를 참조하세요.
FAQ (자주 묻는 질문)
AI 에이전트를 위해 JavaScript가 많은 페이지를 스크래핑하려면 어떻게 해야 하나요?
먼저 정적 추출기(static extractor)로 가져오세요. 마크다운 내용이 매우 짧거나 JavaScript 활성화를 요청하는 경우, 해당 URL을 Web Render와 같은 헤드리스 브라우저에 보내 Chromium에서 페이지를 실행하고 마크다운을 반환하도록 하세요.
페이지가 클라이언트 렌더링(client-rendered)되었는지 어떻게 알 수 있나요?
추출된 텍스트를 살펴보세요: 내용이 거의 없는 본문, 빈 앱 컨테이너, 또는
Web Render는 마크다운(markdown), 텍스트, HTML, 제목(title), 링크 및 선택적 스크린샷을 반환하여 LLM이 페이지를 직접 읽을 수 있게 합니다.
robots.txt가 페이지 접근을 차단하면 요금이 부과되나요?
x402에서는 그렇지 않습니다. Render는 가져오기 전에 URL을 거부(HTTP 422 ROBOTS_DISALLOWED 직접)하며, Pocket 포털을 통해 HTTP 400 UPSTREAM_REJECTED로 표시되고 호출에 대한 요금이 부과되지 않습니다.
페이지 렌더링이 너무 느리면 어떻게 되나요?
Render는 4.2초에서 중단되며, error.code가 TARGET_DEADLINE인 HTTP 200을 반환합니다. 이는 재시도 가능하며 제때 렌더링된 마크다운을 포함할 수 있습니다.
비용은 얼마나 드나요?
Search, Extract 및 Render는 각각 Base의 USDC 또는 Tempo의 MPP로 호출당 $0.005입니다. 가입이나 API 키가 필요하지 않습니다.
시작하기
이미 Extract를 호출하는 모든 루프에 looksClientRendered와 Render 폴백(fallback)을 추가하세요. 에이전트가 아직 URL을 가지고 있지 않을 때는 Web Extract로 시작하고, Web Render로 폴백하며, Web Search를 사용하세요. 더 자세한 내용은 AgentSearch 블로그를 참조하세요.
원래는 AgentSearch 블로그에 게시되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기