본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 05. 11:15

내 셀렉터(Selector)들이 마침내 쉴 수 있도록 AI 기반 웹 스크래핑을 시도해 보았다

요약

전통적인 CSS 셀렉터와 정규 표현식 기반의 웹 스크래핑 방식이 가진 유지보수의 한계를 LLM을 활용한 의미론적 추출 방식으로 해결하는 과정을 다룹니다. HTML 전처리와 퓨샷 프롬프팅을 통해 토큰 비용을 절감하고 데이터 추출의 정확도를 높이는 실전 노하우를 공유합니다.

핵심 포인트

  • 전통적인 셀렉터 방식은 웹사이트 구조 변경 시 유지보수가 매우 어려움
  • HTML 전체를 LLM에 입력할 경우 높은 토큰 비용과 컨텍스트 제한 문제 발생
  • HTML을 단순화된 JSON 트리로 전처리하여 토큰 수를 약 70% 절감 가능
  • 퓨샷 프롬프팅을 통해 모델에게 데이터의 의미론적 맥락을 학습시켜 정확도 향상

몇 달 전, 저는 수십 개의 서로 다른 이커머스 사이트에서 제품 정보를 가져와야 하는 가격 비교 도구를 만들고 있었습니다. 각 사이트는 저마다 정성스럽게 만들어진 HTML 구조를 가지고 있었습니다. 배포될 때마다 price-123abc와 같은 클래스를 가진 중첩된 <div> 태그들이 계속 바뀌었죠. 저의 초기 접근 방식은 전통적인 방식이었습니다. XPath, CSS 셀렉터(Selector), 그리고 약간의 정규 표현식(Regex)을 사용하는 것이었습니다. 처음에는 잘 작동했지만, 곧 문제가 생겼습니다. 그러다 저는 원문 HTML을 LLM(대규모 언어 모델)에 던져주고 추출 작업을 맡길 수 있다는 사실을 발견했습니다. 제가 배운 것들을 공유하겠습니다.

노트북을 던져버리고 싶게 만든 문제

저는 Site A를 위한 스크래퍼(Scraper)를 가지고 있었고, document.querySelector('.product-price')를 사용했습니다. 취약하긴 했지만 몇 달 동안은 잘 작동했습니다. 그러다 Site A가 디자인을 개편했습니다. 셀렉터가 깨졌습니다. 저는 그것을 업데이트했습니다. 일주일 후, 또 다른 디자인 개편이 일어났습니다. 저는 \$\d+\.\d{2}와 같은 패턴을 찾기 위해 정규 표현식(Regex)을 사용하기 시작했습니다. 그러다 누군가 "$5 할인"이라는 배지를 추가했고, 제 정규 표현식은 잘못된 숫자를 가져왔습니다.

저는 단순히 구조가 아니라 가격의 '의미'를 이해할 수 있는 무언가가 필요했습니다. 그때 저는 궁금해졌습니다. GPT-4(또는 다른 언어 모델)가 원문 HTML을 파싱하여 제가 필요한 구조화된 데이터를 줄 수 있을까?

제가 실패했던 시도들 (여러분은 그러지 않도록)

먼저, 제품 페이지의 전체 HTML을 LLM에 직접 전달하며 "제품명, 가격, 재고 여부를 추출해줘"라고 요청해 보았습니다. 두 가지 문제가 있었습니다:

  1. 토큰 제한 (Token limits) – 많은 제품 페이지는 수천 개의 토큰을 가지고 있습니다. GPT-4의 128k 컨텍스트(Context)를 사용하더라도 비용이 치솟았습니다.

또한 토큰을 줄이기 위해 html2text를 사용하여 HTML을 단순화하는 시도도 했습니다. 하지만 이 방식은 구조를 너무 많이 손실시켰습니다. 모델이 본문 내용의 가격과 푸터(Footer) 광고의 가격을 구분할 수 없게 되었습니다.

그다음에는 먼저 정규 표현식(Regex)을 사용하여 가격처럼 보이는 페이지의 부분만 추출한 뒤, 그것을 LLM에 입력하는 방식을 시도했습니다. 그것은 유지보수의 악몽이었습니다. 저는 다시 깨지기 쉬운 패턴을 작성하는 상태로 돌아가 있었습니다.

마침내 성공한 방법: 퓨샷 프롬프트(Few-Shot Prompts)를 이용한 의미론적 추출 (Semantic Extraction)

돌파구는 모델이 보는 '무엇(what)'을 줄이려고 노력하는 대신, 질문하는 '방법(how)'을 개선했을 때 찾아왔습니다. 성공적이었던 접근 방식은 다음과 같습니다:

1단계: HTML을 단순화된 DOM 트리로 전처리하기

가공되지 않은 HTML 대신, 페이지를 일반적인 요소들(제목, 단락, 목록, 표)과 그 텍스트 콘텐츠를 포함하는 깔끔한 JSON 트리로 변환했습니다. 이를 통해 구조를 유지하면서도 토큰 수(token count)를 약 70% 줄일 수 있었습니다.

from bs4 import BeautifulSoup

def simplify_html(html):
...

2단계: 도메인에 맞는 퓨샷 예시(Few-shot examples) 구축하기

제가 원하는 정확한 JSON 출력 형식을 갖춘 제품 페이지 예시를 3~5개 만들었습니다. 그리고 이를 시스템 프롬프트(system prompt)에 하드코딩했습니다. 이것이 핵심이었는데, 모델에게 제 문맥에서 "가격(price)"이 정확히 무엇을 의미하는지(추천 항목이 아닌 첫 번째 제품의 가격)를 알려주었기 때문입니다.

system_prompt = """You are a precise data extractor for e-commerce product pages.
Given simplified HTML, output a JSON object with fields:
- name: product name
...
"""

3단계: 실제로 모델 호출하기

저는 OpenAI의 API를 사용했습니다(하지만 호환 가능한 엔드포인트라면 무엇이든 교체할 수 있습니다. 로컬 모델조차도 가능합니다). 핵심은 결정론적인 추출(deterministic extraction)을 위해 온도를 0(temperature to 0)으로 설정하는 것이었습니다.

import openai

def extract_product_info(simplified_html):
...

네, 정말 이토록 간단합니다. 그리고 제가 던져준 대부분의 페이지에 대해 놀라울 정도로 신뢰할 수 있는 성능을 보여주었습니다.

배운 점 (그리고 트레이드오프)

이 접근 방식이 만능 해결책(silver bullet)은 아닙니다. 제가 발견한 사실은 다음과 같습니다:

  • 비용 (Cost): GPT-4를 사용할 경우 추출당 약 $0.01–0.03이 소요됩니다. 1,000개의 제품을 일회성으로 스크래핑할 경우 $10–30가 들며, 이는 깨지기 쉬운 셀렉터(Selector)를 유지보수하는 제 시간보다 저렴합니다. 하지만 빈도가 높은 피드(Feed)의 경우 비용이 빠르게 누적됩니다.
  • 지연 시간 (Latency): LLM 호출에는 1~3초가 소요됩니다. 대규모 실시간 스크래핑을 위해서는 병렬 처리(Parallelism)나 배치 처리(Batch processing)가 필요할 것입니다.
  • 환각 (Hallucinations): Few-shot 예시를 사용하더라도, 페이지 레이아웃이 혼란스러울 경우 모델이 가끔 가격을 지어내기도 합니다. 저는 추출된 가격이 합리적인 범위 내의 숫자인지 확인하는 후처리(Post-processing) 단계를 추가했습니다.
  • 컨텍스트 길이 (Context length): 매우 긴 페이지(예: 50개의 제품이 포함된 카테고리 목록)의 경우, 단순화된 HTML을 섹션별로 청크(Chunk)로 나누어 모델에게 각 청크에서 추출하도록 요청한 뒤 결과를 병합했습니다.

또한 https://ai.interwestinfo.com/와 같이 이러한 트레이드오프(Trade-offs)를 추상화하여 처리해 주는(백그라운드에서 청킹과 검증을 수행하는) 전문 API를 실험해 보기도 했습니다. 하지만 솔직히 말해서, 단순화된 DOM 구조를 활용한 Few-shot 프롬프팅(Prompting)이라는 핵심 기술이 차이를 만들어냈습니다.

이 방식을 사용하면 안 되는 경우

다음과 같은 경우에는 이 접근 방식이 과할 수 있습니다:

  • 사이트에 안정적이고 문서화가 잘 된 API가 있는 경우 (대신 그것을 사용하세요!)
  • 수백만 개의 페이지를 스크래핑해야 하는 경우 (비용과 지연 시간이 감당하기 어려워집니다)
  • 절대 변하지 않는 완벽한 셀렉터를 가지고 있는 경우 (유니콘은 희귀합니다)

그리고 봇(Bot) 사용을 명시적으로 금지하는 사이트를 스크래핑하는 경우, robots.txt를 준수하고 허가를 요청하는 것을 고려해야 함을 기억하세요. 이 기술은 법을 어기지 않도록 쉽게 만들어 주지만, 그렇다고 해서 면죄부를 주는 것은 아닙니다.

다음에 다시 한다면 다르게 할 점

처음부터 LLM 기반 접근 방식을 사용할 것입니다. 정규 표현식(Regex)과 CSS 셀렉터를 디버깅하며 보낸 시간들은 매몰 비용(Sunk cost)이었습니다. 또한 더 많은 검증 단계를 추가할 것입니다. 여러 후보를 추출하여 호출 간의 투표(Vote)를 거치거나, 도메인이 충분히 좁다면 구조화된 추출을 위해 소규모 로컬 모델(Fine-tuned BERT 등)을 사용할 것입니다.

여러분의 차례입니다

이제 언어 모델(Language Models)이 인간처럼 HTML을 읽을 수 있게 되면서, 판도가 바뀌었습니다. 하지만 저는 여전히 실험 중입니다. 여러분은 전처리(Pre-process)를 다르게 하시나요? 다른 모델을 사용하시나요? 아니면 구식 셀렉터(Selectors)와 기도하는 마음을 고수하시나요? 여러분의 스크래핑 스택(Scraping Stack)이 어떻게 구성되어 있는지 정말 궁금합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0