AI 에이전트가 JavaScript로 렌더링된 페이지를 읽는 방법 (추출 결과가 비어올 때)
요약
AI 에이전트가 JavaScript로 렌더링된 SPA 페이지를 읽을 때 빈 결과를 받는 문제를 다룹니다. 이 글은 추출기(Extract)만으로는 내용 파악이 어렵고, 실제 내용을 얻으려면 웹 렌더링(Web Render) 과정이 필요함을 설명합니다. 따라서 'Extract 시도 후 Render 폴백' 전략을 권장하며, 이를 구현하는 방법을 제시합니다.
핵심 포인트
- SPA 페이지는 JS 실행 없이 Extract만으로는 내용 파악이 어렵습니다.
- 추출기(Extract)가 빈 결과를 반환하면 웹 렌더링(Web Render)으로 폴백해야 합니다.
- Render는 헤드리스 Chromium을 사용해 클라이언트 측 스크립트 실행을 보장합니다.
- 비용 효율성을 위해 Extract를 먼저 시도하고 필요할 때만 Render를 사용하는 것이 합리적입니다.
AI 에이전트가 처리하는 자바스크립트 렌더링 페이지 읽는 방법 (Extract 결과가 비어올 때)
AI 에이전트에게 싱글 페이지 앱(SPA)이나 자바스크립트가 렌더링된 페이지를 읽게 하려고 할 때, 가장 흔한 문제는 빈 결과입니다. 에이전트는 추출기(extractor)를 호출하고, 페이지 제목과 함께 자바스크립트를 활성화하라는 한 줄의 메시지를 받습니다. 그런 다음 자신 있게 "페이지에 내용물이 없습니다"라고 보고합니다. 아무것도 실패하지 않았습니다. 추출기는 단순히 텍스트가 없는 HTML에 대해 자신의 역할을 수행했을 뿐입니다.
이 글은 바로 그 순간에 관한 것입니다. 코드를 통해 추출기(Extract) 결과가 실제 페이지라기보다는 자바스크립트 셸(shell)이라는 것을 어떻게 판단할 수 있는지, 그리고 에이전트가 다음에 무엇을 해야 하는지를 다룹니다. 우리는 감지 확인 절차, 먼저 시도하고 필요할 때만 Web Extract를 사용한 후 Web Render로 폴백(fallback)하는 작은 Python 함수, 각 도구가 반환하는 오류 코드, 그리고 언제 Render가 잘못된 답변인지에 대해 다룹니다. 모든 호출은 Pocket Agentic Portal에서 $0.005 USDC이며, Base 또는 MPP를 통해 x402로 지불되며 가입이 필요 없습니다.
전체 스크래핑 파이프라인의 TypeScript 버전을 원한다면 AI 에이전트를 위한 자바스크립트가 많은 페이지 스크랩 방법을 참조하세요. 이 글은 에이전트의 도구 내부에서 읽기 결정에 초점을 맞춥니다.
왜 SPA에서 Extract가 빈 결과를 반환하는가
Extract는 서버가 보내는 HTML을 가져와 깨끗한 마크다운으로 변환합니다. 서버 렌더링된 페이지의 경우, 해당 HTML은 이미 기사 내용을 담고 있으므로 마크다운이 완성됩니다.
단일 페이지 애플리케이션(SPA)은 종종 거의 비어 있는 문서를 전송합니다: <head>, <div id="root"></div>, <noscript> 알림, 그리고 브라우저에서 페이지를 구성하는 JavaScript 번들입니다. Extract는 해당 JavaScript를 실행하지 않으므로, 받은 것을 그대로 변환합니다. 그 결과는 얇거나 비어 있으며, 수신한 HTML에 대해서는 정확합니다.
Render는 헤드리스 Chromium에서 URL을 로드하고 스크립트가 실행되도록 한 다음, 브라우저가 최종적으로 갖게 된 내용을 반환합니다: markdown, text, html, title, links 및 선택적 스크린샷입니다. 이것이 클라이언트 렌더링된 페이지를 위한 도구입니다. 또한 호출당 $0.005로 비용이 동일하므로, 모든 것을 렌더링하는 것보다 'Extract를 시도한 후 Render'가 합리적인 기본값인 이유입니다.
얇거나 비어 있는 Extract 결과를 감지하는 방법
Extract는
3. 빈 루트 컨테이너(An empty root container). 전형적인 SPA 셸은 내부에 아무것도 없는 단일 <div id="root"></div> 또는 <div id="app"></div>입니다. 이 div는 마크다운에서 어떠한 텍스트도 생성하지 않기 때문에, 실제로는 제목만 있고 본문이 없거나, 본문 전체가 탐색 레이블과 푸터로만 구성된 것처럼 보입니다. "제목은 존재하지만 본문은 실질적으로 비어 있음"을 빈 루트 div의 마크다운 시그니처로 간주하십시오.
피해야 할 두 가지 사항: '얇은 결과(thin result)'에 대해 추출(Extract)을 재시도하지 마십시오 (이는 동일한 HTML을 다시 가져오고 비용만 또 지불하게 만듭니다). 또한 웹사이트 오류를 "빈 내용"으로 취급해서는 안 됩니다. 오류는 별도의 분기 처리 영역이며, 아래에서 다룹니다.
Python 대체 방법: 먼저 추출하고, 그 다음 렌더링하기 (A Python fallback: Extract first, then Render)
아래 함수는 deep research agent 가이드에서 사용된 것과 동일한 call_service 래퍼를 사용합니다: pay_fetch는 x402 v2 클라이언트(거기에 목업됨)이며, call_service는 포털 실패 또는 전달된 data 중 하나를 반환합니다. 요청 및 응답 형태는 해당 게시물과 일치하며, 두 도구 모두 url, formats, 그리고 max_chars를 받으며, url(리디렉션 후 최종 URL), title, markdown, 그리고 error(성공 시 null)를 반환합니다.
# read_js_page.py
from __future__ import annotations
...
반환된 딕셔너리를 모델에 도구 결과(tool result)로 바로 전달하십시오. skip은 다른 소스를 선택하도록 지시하며, retry_later는 "다른 차례에"를 의미할 뿐, 결코 "지금 루프"를 의미하지 않습니다. EMPTY_PAGE는 서비스가 반환하는 코드가 아니라 로컬 코드입니다.
각 오류 코드가 의미하는 바
• TARGET_TIMEOUT (Extract). 사이트가 마감 시간 내에 Extract에게 완전한 답변을 제공하지 못했습니다. 페이지를 건너뛰거나 다른 결과를 선택하세요.
• UNSUPPORTED_CONTENT (Extract). 해당 URL은 HTML Extract가 변환할 수 없습니다. 렌더링해도 도움이 되지 않습니다.
• TARGET_DEADLINE (Render only). Render가 하드 마감 시간에 도달했습니다. 만약 일부 콘텐츠가 제시간에 렌더링되었다면, 부분적인 markdown이 포함된 HTTP 200을 받게 되니 이를 유지하고 '부분적(partial)'으로 표시하세요. 아무것도 렌더링되지 않았다면, 재시도 가능하며 비용이 청구되지 않는 HTTP 422 TARGET_DEADLINE을 받습니다.
• ROBOTS_DISALLOWED / UPSTREAM_REJECTED. 사이트의 robots.txt가 Render를 차단합니다. 더 자세한 내용은 아래를 참조하세요.
code와 retryable에 따라 분기하고, 절대 message 텍스트에 의존하지 마세요. 전체 목록은 AI 에이전트 웹 도구 오류 처리에서 확인할 수 있습니다.
비용 관련 참고 사항
정적 페이지는 Extract 호출 1회에 $0.005가 소요됩니다. JavaScript 페이지는 Extract 호출 1회와 Render 호출 1회를 합쳐 총 2회, $0.01이 소요됩니다. robots.txt 거부는 비용이 들지 않습니다. 만약 특정 호스트가 클라이언트 측에서 렌더링된다는 것을 이미 알고 있다면(지난번에 내용이 부실하게 나왔다면), Render를 직접 호출하여 첫 번째 호출을 절약하세요. 따라서 청구서가 모델의 판단에 의존하지 않도록 코드 내에서 작업당 전달되는 최대 호출 횟수를 제한하는 것이 좋습니다.
robots.txt 준수 (거부 비용 지불 방지)
Render는 robots.txt를 준수합니다. 만약 사이트가 자신의 사용자 에이전트를 금지하면, Render는 아무것도 가져오기 전에 해당 URL을 거부합니다. 직접 호출할 경우, 이는 ROBOTS_DISALLOWED가 포함된 HTTP 422로 나타납니다:
{"error": {"code": "ROBOTS_DISALLOWED", "message": "robots.txt disallows this URL for AgentSearchRender.", "retryable": false, "request_id": "…"}}
Pocket 포털을 통해 접근할 경우 HTTP 400 UPSTREAM_REJECTED로 표시되며 비용이 청구되지 않습니다. 올바른 에이전트의 행동은 답변을 수용하는 것입니다. 즉, 사이트가 봇에게 해당 페이지 로드를 금지했으므로 다른 소스로 넘어가야 합니다. 재시도하거나, 도구를 순환시키거나(rotate tools), 이 장애물을 우회하려 시도하지 마세요.
Render를 사용하지 말아야 할 때
Render는 기본값이 아닌 폴백(fallback)입니다. 다음 경우에 건너뛰세요:
- 이미 추출된 텍스트가 반환된 경우. 마크다운이 기사처럼 보인다면, 한 번의 호출로 충분합니다.
- 페이지에 로그인이 필요한 경우. Render는 로그인 처리를 하지 않습니다. 로그인 벽은 로그인 양식으로 표시됩니다.
- 콘텐츠가 HTML이 아닌 경우. PDF나 이미지가 Extract에서
UNSUPPORTED_CONTENT를 반환하더라도 브라우저에서는 개선되지 않습니다. - 사이트가 봇을 차단하는 경우. robots.txt 거부 또는 봇 검사는 사이트의 답변입니다. 다른 소스를 선택하세요.
- 공개 JSON 또는 피드 URL이 있는 경우. 많은 SPA(Single Page Application)는 공개 API 엔드포인트에서 텍스트를 로드합니다. 이미 알고 있다면, 해당 주소를 단순하게 가져오는 것이 더 간단합니다.
- 아직 URL이 없는 경우. 먼저 Web Search를 사용한 다음, 스니펫이 관련 있어 보이는 결과만 읽으세요.
클라이언트를 작성하지 않고 사용하는 방법
에이전트가 MCP(Multi-Client Platform) 클라이언트에서 실행되는 경우, @agentsearchhq/agentsearch-mcp 1.3.x는 Search, Extract 및 Render를 도구로 노출하며, Search는 기본적으로 무료입니다. 프레임워크 사용자는 langchain-agentsearch(PyPI), @agentsearchhq/langchain(npm), crewai-agentsearch, llama-index-tools-agentsearch, openai-agents-agentsearch 또는 n8n-nodes-agentsearch부터 시작할 수 있습니다. 어떤 래퍼(wrapper)를 사용하든, 모델의 지침에 동일한 규칙을 제공하세요: 먼저 Extract를 호출하고, 마크다운이 빈약하거나 JavaScript가 필요한 경우에만 Render를 호출합니다.
FAQ
AI 에이전트가 싱글 페이지 애플리케이션(SPA)을 읽으려면 어떻게 해야 하나요?
먼저 Extract를 호출하세요. 마크다운이 매우 짧거나,
$0.005는 페이지 추출(Extract)이 읽을 수 있는 경우의 비용이며, 렌더링(Render)이 필요할 때는 $0.01 (2회 호출 시)가 발생하고, robots.txt가 렌더링을 거부하면 비용은 없습니다.
렌더링 시간 초과 시 어떻게 되나요?
만약 일부 콘텐츠가 렌더링되었다면, TARGET_DEADLINE이 포함된 HTTP 200 응답과 부분적인 마크다운(markdown)을 받게 됩니다. 이 내용을 유지하고 불완전한 것으로 표시하세요. 아무것도 렌더링되지 않았다면, 재시도 가능하며 비용이 청구되지 않는 HTTP 422 TARGET_DEADLINE을 받습니다.
Render가 로그인 뒤나 봇 검사 페이지의 콘텐츠를 읽을 수 있나요?
아니요. 로그인하지 않으며 robots.txt를 준수합니다. 다른 소스를 선택하세요.
시작하기
이미 Extract를 호출하는 모든 에이전트 도구에 read_js_page를 추가하세요. Web Extract로 시작하고, Web Render로 폴백(fall back)하며, Web Search를 통해 URL을 찾으세요. 더 많은 가이드는 AgentSearch 블로그에서 확인할 수 있습니다.
원래는 AgentSearch 블로그에 게시되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기