DOM 접근성 트리 추출: 동적 웹 테이블에 대한 LLM용 신뢰할 수 있는 방법
요약
동적 웹 페이지의 테이블 데이터를 LLM이 정확하게 처리할 수 있도록 DOM 접근성 트리를 활용하여 데이터를 추출하는 최적의 방법론을 제시합니다. 정적 페치, OCR, 비전 모델의 한계를 극복하기 위해 Playwright와 같은 헤드리스 브라우저를 사용하여 JavaScript 렌더링 이후의 의미론적 구조를 추출하는 과정을 설명합니다.
핵심 포인트
- 정적 HTML 페치, OCR, 비전 모델 방식은 현대적인 클라이언트 사이드 렌더링 환경에서 실패하거나 비용이 높음
- 접근성 트리(Accessibility Tree)를 활용하면 픽셀 의존성 없이 구조화되고 의미론적인 데이터 추출 가능
- Playwright를 사용하여 JavaScript 실행 및 네트워크 유휴 상태를 기다린 후 inner_text()를 통해 데이터를 확보하는 것이 권장됨
- 추출된 텍스트를 파일로 기록하여 감사 추적(audit trail) 및 재파싱이 가능하도록 설계해야 함
상태: 2026년 기준 현재 사용 가능한 최선의 기술. 임시 방편이 아닌 표준 관행으로 취급하십시오.
문제점
세 가지 단순한 접근 방식은 현대적인 사이트에서 실패합니다:
- view-source / 정적 페치 (static fetch) — JavaScript가 실행되기 전의 서버 HTML을 반환합니다. JS로 렌더링된 테이블은 빈 <tbody> 태그만 보여줍니다.
- 스크린샷 + OCR — 느리고, 픽셀에 의존하며, 취약합니다. 숫자 데이터에서 오류를 가중시킵니다.
- 스크린샷 + 비전 모델 (vision model) — 비용이 많이 들고, 컨텍스트가 제한적이며, 하나의 뷰포트(viewport)보다 큰 테이블에서는 실패합니다.
근본 원인: 웹이 클라이언트 사이드 렌더링 (client-side rendering)으로 전환되었습니다. 데이터는 HTML이 아니라 JavaScript 런타임 상태 (runtime state)에 존재합니다.
방법론
직관적 개념: 다음 과정을 프로그래밍 방식으로 구현한 것과 같습니다:
테이블 강조 표시 → 복사 → 메모장에 붙여넣기 → Excel로 가져오기 → 관련 없는 열 삭제 → 정렬 및 계산.
단계:
- 헤드리스 브라우저 (headless browser)에서 페이지 로드 (Playwright 권장) — JavaScript가 실행되고 테이블이 렌더링됩니다.
- 모든 드롭다운이나 필터를 상호작용하고, networkidle 상태를 기다립니다.
- 테이블 요소에 대해 inner_text()를 호출합니다.
- 추출된 텍스트를 파일에 기록합니다 (감사 추적(audit trail) 및 재파싱 가능).
- Python에서 파싱 — 줄바꿈/탭을 기준으로 분할하고, 숫자로 형변환(cast)하며, 필터링 및 계산합니다.
작동 원리
접근성 트리 (accessibility tree)는 구조화되어 있고 의미론적(semantic)이며, 픽셀에 의존하지 않습니다. 이미 브라우저에 의해 파싱되어 있으며 빠릅니다. 숫자 데이터에 대한 OCR 전사 오류가 발생하지 않습니다.
의사 코드
python from playwright.sync_api import sync_playwright import re with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto(URL, wait_until="networkidle") page.select_option("select#view-filter", label="All Cities") page.wait_for_load_state("networkidle") table_text = page.query_selector("table").inner_text() browser.close()
with open("table_output.txt", "w") as f: f.write(table_text)
lines = table_text.strip().split("\n")
rows = [line.split("\t") for line in lines[1:] if line.strip()]
temps = [float(re.sub(r"[^\\\d.-]", "", r[2])) for r in rows if r[2].strip()]
print(f"Below 32°F: {sum(t < 32 for t in temps)}")
print(f"Above 100°F: {sum(t > 100 for t in temps)}")
실제 예시 출처: timeanddate.com, 472개 도시 날씨 표, “Somewhat Popular” 보기.
• 실행 시간: ~8초
• 32°F 미만 도시: 47곳
• 100°F 초과 도시: 12곳
• OCR 오류: 0
한계점
• 실제 브라우저 런타임(Playwright/Puppeteer) 필요
• 일부 사이트는 헤드리스 자동화 차단
• Canvas로 렌더링된 표는 page.accessibility.snapshot() 대체 방법 필요
• 무한 스크롤은 스크롤 이벤트 시뮬레이션 필요
• 공식 API가 존재하는 경우 항상 선호함
상세 가이드 및 예제 전체 내용은 GitHub에서 확인 가능: https://github.com/hottbunny/LLM-AI-Perplexity-Skills-and-Updates/blob/hottbunny-tested-works-htmlsearchtablecrawldataretrivalskill/dom_extraction_method.md
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기