스크래퍼가 제대로 작동하는 척하지 마세요: AI 에이전트를 위한 정직한 JSON
요약
AI 에이전트를 위한 스크래핑 시스템은 단순히 데이터를 추출하는 것을 넘어, 실패 상황을 정직하게 보고해야 합니다. 로그인 장벽이나 캡차와 같은 실패 원인을 명확한 메타데이터로 제공해야 에이전트가 잘못된 결정을 내리지 않습니다.
핵심 포인트
- 단순한 JSON 응답이 유효한 데이터 추출을 보장하지 않음
- 실패 유형(login_required, captcha_required 등)을 명시적으로 분류해야 함
- 에이전트의 의사결정을 위해 충분한 메타데이터 보존이 필수적임
- 스키마를 채우기 위해 데이터를 지어내는 행위는 지양해야 함
대부분의 스크래퍼 데모는 의도치 않게 거짓말을 합니다.
그들은 해피 패스(happy path)를 보여줍니다: 하나의 URL, 하나의 깨끗한 페이지, 하나의 깔끔한 JSON 객체 말이죠. 그러다 첫 번째 실제 사용자가 마켓플레이스 검색 페이지, 로그인 장벽(login wall), JavaScript 셸(JavaScript shell), 속도 제한(rate-limited)이 걸린 제품 페이지, 또는 모든 페치(fetch) 경로에 대해 서로 다른 HTML을 제공하는 사이트를 시도하게 됩니다.
응답은 여전히 JSON으로 돌아오기 때문에 모두가 안심합니다.
그것이 함정입니다. JSON 응답이 유용한 추출(extraction)과 동일한 것은 아닙니다.
에이전트가 싫어하는 실패 모드
AI 에이전트에게는 단순히 스크래핑된 텍스트만 필요한 것이 아닙니다. 그들은 무엇이 일어났는지 알아야 합니다.
잘못된 추출 출력은 다음과 같습니다:
{
"title": "Example product",
"price": "$29.99",
...
소스(source)를 조사하여 해당 페이지가 로그인 프롬프트, 봇 챌린지(bot challenge), 또는 빈약한 JavaScript 셸이었다는 사실을 발견하기 전까지는 괜찮아 보입니다. 추출기(extractor)는 스키마(schema)가 요청되었기 때문에 스키마를 채워 넣은 것입니다. 참으로 도움이 되네요. 마치 주방이 불타고 있는 동안 작은 노래를 흥얼거리는 화재 경보기처럼 말입니다.
더 나은 추출 출력은 데이터와 신뢰도(confidence), 그리고 실패 클래스(failure class)를 분리합니다:
{
"status": "failed",
"failure_type": "login_required",
...
이것은 덜 화려합니다. 하지만 훨씬 더 유용합니다.
유용한 계약은 "무엇이든 스크래핑하기"가 아닙니다
"무엇이든 스크래핑하기"는 대개 립스틱을 바른 경고 라벨에 불과합니다.
에이전트 워크플로(agent workflows)를 위해서는 다음과 같은 더 나은 계약이 필요합니다:
- 페이지가 충분한 증거를 제공할 때 구조화된 데이터(structured data)를 반환할 것.
- 그렇지 않을 때는 구체적이고 정직한 실패(failure)를 반환할 것.
- 호출자(caller)가 다음에 무엇을 할지 결정할 수 있도록 충분한 메타데이터(metadata)를 보존할 것.
- 프롬프트(prompt)가 정중하게 요청했다고 해서 절대로 필드를 지어내지 말 것.
이는 이커머스(ecommerce), 리드 인리치먼트(lead enrichment), 가격 모니터링, 경쟁사 추적, 조달(procurement), 그리고 내부 연구 에이전트에게 매우 중요합니다. 만약 에이전트가 "제품 없음", "페이지 차단됨", "로그인 필요", 그리고 "파서(parser)의 추측" 사이의 차이를 구분할 수 없다면, 에이전트는 아주 태연하게 잘못된 결정을 내릴 것입니다.
정직한 실패란 무엇을 의미하는가
정직한 추출 시스템은 일반적인 실패 사례를 명시적으로 분류해야 합니다:
login_required: 공개적인 수집 (public fetch) 과정에서 로그인 벽에 부딪힘.captcha_required: 대상 사이트가 캡차 (CAPTCHA) 인증을 요구함.access_denied: 대상 사이트가 접근을 거부함.thin_public_content: 보이는 공개 페이지에 유용한 데이터가 충분히 포함되어 있지 않음.not_found: 페이지가 실제로 존재하지 않는 것으로 보임.timeout: 대상 사이트나 렌더링 경로가 할당된 예산(budget) 내에 완료되지 않음.unsupported_source: 입력값이 허용된 수집 정책 (fetch policy) 범위를 벗어남.
마지막 항목이 중요합니다. 어떤 소스들은 권한, 계정, 피드 (feed), 파트너십, 또는 고객이 제공한 내보내기 (export) 파일이 필요합니다. 그렇지 않은 척하는 것이 바로 "자동화"가 평판 훼손으로 변질되는 방식입니다.
이것이 왜 MCP 문제이기도 한가
MCP (Model Context Protocol)는 에이전트가 도구 (tools)를 호출하는 것을 더 쉽게 만듭니다. 이는 좋은 일입니다.
하지만 에이전트가 잘못된 도구를 확신을 가지고 호출하는 것도 더 쉽게 만듭니다. 이는 좋지 않은 일입니다.
만약 MCP 도구가 "이 페이지에서 제품 데이터를 추출하라"고 말한다면, 호출자는 단순한 텍스트 덩어리 이상의 것이 필요합니다. 에이전트에게 이 답변을 사용해도 안전한지 알려줄 수 있는 결과 형태 (result shape)가 필요합니다.
제대로 된 MCP 추출 응답은 다음을 노출해야 합니다:
- 정적 HTML (static HTML), 브라우저 렌더링 (browser render), 결정론적 파서 (deterministic parser), 또는 LLM 보조 추출 (LLM-assisted extraction)과 같이 사용된 모드 (mode),
- 신뢰도 (confidence),
- 아이템 개수 (item counts),
- 차단되었을 때의 실패 유형 (failure type),
- 결과가 보이는 페이지의 증거로부터 도출되었는지 여부,
- 제한된 다음 단계 (a bounded next step).
이것이 에이전트에게 결정 경계 (decision boundary)를 제공합니다. 이것이 없다면, 에이전트는 그저 거짓말을 하류 (downstream)로 더 빠르게 퍼뜨릴 뿐입니다.
실무적인 패턴
실제 운영 환경의 추출을 위해, 저는 다음과 같은 대략적인 흐름을 선호합니다:
- 가장 저렴하고 안전한 경로를 통해 페이지를 가져옵니다 (Fetch).
- LLM에게 무엇인가를 묻기 전에 명백한 차단 사례를 분류합니다.
- 알려진 구조(JSON-LD, 테이블, 제품 카드, 메타데이터, 피드 등)에 대해 결정론적 파싱 (deterministic parsing)을 시도합니다.
- 페이지에 실제로 필요한 경우에만 브라우저 렌더링을 사용합니다.
- LLM에게 누락된 증거를 환각 (hallucinate)하게 하지 말고, 증거를 구조화하도록 요청합니다.
- 신뢰도와 실패 메타데이터를 첨부합니다.
- 할당량 (quota)과 과금 (billing)이 무작위적인 제공자 시도가 아닌, 성공적인 유용한 작업에 대해서만 계산되도록 합니다.
지루한 부분들이 바로 제품의 핵심입니다.
단 한 페이지를 추출하는 데모를 만드는 것은 누구나 할 수 있습니다. 어려운 부분은 호출자(caller)가 신뢰할 수 있는 방식으로 시스템이 실패하도록 만드는 것입니다.
예시: 에이전트가 읽을 수 있는 디스커버리 (agent-readable discovery)
이는 도구(tools)가 발견되는 방식에도 영향을 미칩니다.
만약 에이전트가 읽을 수 있는 디렉토리나 서비스 레지스트리 (service registry)를 운영한다면, "웹 스크래퍼 API"라고 적힌 모호한 카드만으로는 충분하지 않습니다. 에이전트는 다음 사항을 알아야 합니다:
- 서비스가 어떤 입력을 받는지,
- 어떤 출력 형태 (output shape)를 반환하는지,
- 무엇을 수행하기를 거부하는지,
- 어떤 인증 (authentication)이 필요한지,
- 첫 번째 안전한 테스트 호출 (test call)이 어떤 모습인지,
- 실패 클래스 (failure classes)가 무엇을 의미하는지.
이것이 제가 로고 벽 (logo walls)보다 서비스 카드 (service cards)를 선호하는 이유입니다. 인간은 브랜딩으로부터 많은 것을 추론할 수 있습니다. 하지만 에이전트에게는 계약 (contracts)이 필요합니다.
작은 데모 경로
저는 이를 MCP 인터페이스를 갖춘 웹 추출 API인 Haunt API에 구축하고 있으며, 에이전트가 읽을 수 있는 서비스 디렉토리인 OpenInvoke를 통해 이를 등록하고 있습니다.
유용한 시작점:
- Haunt MCP 웹 추출 유스케이스: https://hauntapi.com/use-cases/mcp-server-for-web-scraping?utm_source=devto&utm_medium=article&utm_campaign=seven_day_push_2026_06_day1_devto&utm_content=honest_json_agents
- Haunt 문서 및 데모 경로: https://hauntapi.com/docs?utm_source=devto&utm_medium=article&utm_campaign=seven_day_push_2026_06_day1_devto&utm_content=docs_demo
- OpenInvoke 에이전트 읽기 가능 디렉토리 아이디어: https://openinvoke.com/agent-readable-api-directory/?utm_source=devto&utm_medium=article&utm_campaign=seven_day_push_2026_06_day1_devto&utm_content=service_cards
그 제안은 의도적으로 "모든 웹사이트를 우회한다"라고 말하지 않습니다. 그것은 우리가 하려는 게임이 아닙니다.
더 나은 게임은 다음과 같습니다: 정당하게 이용 가능한 데이터를 추출하고, 이용 불가능할 때는 불가능하다고 말하며, 차단된 페이지를 마치 제품인 것처럼 속이지 않고 에이전트(Agent)가 추론할 수 있는 결과를 제공하는 것입니다.
이것은 덜 마법 같을지 모릅니다. 하지만 또한 당신의 워크플로(Workflow)를 망가뜨리지 않는 방식이기도 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기