본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 05. 17:20

내가 정규표현식(Regex)을 포기하고 웹 스크래핑(Web Scraping)에 AI를 사용하기 시작한 이유

요약

기존의 CSS 선택자나 XPath 기반 웹 스크래핑이 HTML 구조 변경에 취약하다는 문제를 해결하기 위해 LLM을 활용한 데이터 추출 방식을 제안합니다. HTML의 구조적 변화에 관계없이 의미론적 정보를 바탕으로 JSON 데이터를 안정적으로 추출할 수 있습니다.

핵심 포인트

  • 기존 규칙 기반 스크래핑은 웹사이트 레이아웃 변경 시 유지보수 비용이 높음
  • LLM은 HTML 태그 구조가 아닌 의미론적 단서를 통해 데이터를 파악함
  • 자연어 지시사항을 통해 복잡한 HTML에서 구조화된 JSON 추출 가능
  • 마크업 변경에 대한 높은 회복탄력성(Resilience) 확보

저는 수년 동안 웹사이트를 스크래핑해 왔습니다. 저에게 그것은 API가 없는 사이트에서 축구 경기 통계를 수집하는 방법으로 시작되었습니다. 그러다 제품 가격을 추적하는 사이드 프로젝트가 되었고, 결국 악몽이 되었습니다.

지난달, 저는 일련의 이커머스 제품 페이지에서 구조화된 데이터(Structured data)를 추출하려고 시도했습니다. HTML은 엉망이었습니다. 일관성 없는 클래스 이름(Class names), 중첩된 div soup, 그리고 제품 카테고리에 따라 레이아웃이 바뀌는 간헐적인 서버 사이드 렌더링(Server-rendered) 컴포넌트까지 있었습니다. 저의 초기 접근 방식은 무엇이었을까요? CSS 선택자(CSS selectors), XPath, 그리고 하룻밤 사이에 아무것도 변하지 않기를 바라는 기도였습니다.

결국 깨졌습니다. 또, 그리고 또 말이죠. 저는 가라앉는 듯한 기분을 느꼈습니다. 데이터를 실제로 사용하는 시간보다 스크래퍼(Scraper)를 유지보수하는 데 더 많은 시간을 쓰고 있었습니다.

효과가 없었던 시도들

저는 BeautifulSoup과 lxml로 시작했습니다. 한동안은 단순한 soup.select('div.product-price')가 잘 작동했습니다. 그러다 소매업체가 제품 페이지를 재설계했습니다. 가격은 이제 price_4f3d와 같은 동적 클래스를 가진 <span> 태그 안에 들어있었습니다. 저는 부분 일치(Partial matches)를 사용하는 새로운 선택자를 작성했습니다. 3일 후, 그들은 특정 카테고리에 대한 레이아웃을 변경했고, 스크립트는 NoneType 에러를 던졌습니다.

다음으로, 저는 속성 기반 추출(Attribute-based extraction)로 넘어갔습니다: `find('div', attrs={'data-testid': 'price'}

결국 효과를 본 방법

저는 한 걸음 물러나 스스로에게 물었습니다: “내가 정말로 하려는 것이 무엇인가?” 저는 임의의 HTML에서 특정 필드(이름, 가격, 재고 여부, 설명)를 추출하고 싶습니다. 문제는 HTML 구조입니다. 제가 관심을 두는 것은 바로 _의미론적 정보 (semantics)_입니다.

그래서 저는 추출 작업을 위해 대규모 언어 모델 (LLM)을 사용하기 시작했습니다. 깨지기 쉬운 규칙을 작성하는 대신, 원본 HTML을 AI 모델에 전달하고 제가 필요한 필드가 포함된 JSON 객체를 반환하도록 요청합니다. 모델은 “제품 가격을 숫자로 추출해줘”와 같은 자연어 지시사항을 이해하며, HTML 구조는 무시합니다.

다음은 Python을 사용한 대략적인 아이디어입니다 (OpenAI의 API를 예시로 사용):

import openai
from bs4 import BeautifulSoup
import json
...

{clean_html[:3000]} # 비용 절감을 위해 입력 길이 제한

"""
Return only JSON.
"""
...

json ...

result_text = result_text.strip().removeprefix("```")

...

try:
    data = json.loads(result_text)
    print(data)
except json.JSONDecodeError:
    print("Failed to parse AI response")

알고 있습니다, 알고 있어요. “AI를 투입한다”는 말이 과장처럼 들릴 수도 있다는 것을요. 하지만 제 말을 끝까지 들어보세요.

이 방법이 작동하는 이유 (그리고 작동하지 않는 이유)

  • 마크업 변경에 대한 회복탄력성 (Resilience to markup changes): LLM은 가격이 <div>에 있는지 <span>에 있는지 신경 쓰지 않습니다. 모델은 가시적인 텍스트와 그 근처에 있는 “Price:”와 같은 의미론적 단서들을 읽습니다.
  • 확장의 용이성: 배송비를 추출하고 싶나요? JSON 스키마(schema)에 추가하고 프롬프트(prompt)에 언급하기만 하면 됩니다. 새로운 셀렉터(selector)를 만들 필요가 없습니다.
  • 다양한 소스에서 작동: 저는 최소한의 조정만으로 10개의 서로 다른 소매업체 사이트에서 동일한 프롬프트를 사용해 보았습니다.

하지만 실제적인 트레이드오프 (trade-offs)도 존재합니다:

  • 비용 (Cost): 각 API 호출은 1센트의 아주 적은 비용이 듭니다 (gpt-4o-mini의 경우, 1,000페이지당 몇 펜스 수준). 소규모 스크래핑 (Scraping)에는 괜찮습니다. 하지만 수백만 페이지를 처리할 때는 비용이 누적됩니다.
  • 지연 시간 (Latency): 가장 빠른 모델조차 1~3초가 소요됩니다. 수십 개의 페이지를 실시간으로 스크래핑하기에는 적합하지 않습니다.
  • 환각 (Hallucinations): 모델이 그럴듯해 보이지만 실제로는 존재하지 않는 가격을 지어낼 수 있습니다. 실제 가격이 이미지 안에 숨겨져 있을 때 모델이 "$19.99"라고 반환하는 것을 본 적이 있습니다. 검증 (Validation)이 필요합니다.
  • HTML 크기 (HTML size): LLM (대규모 언어 모델)에는 컨텍스트 제한 (Context limits)이 있습니다. 페이지 전체를 전달할 수 없는 경우가 많습니다 (특히 무거운 JavaScript가 포함된 경우). 저는 노이즈를 제거하고 길이를 자르는 전처리 (Pre-process) 과정을 거칩니다.

배운 점 (Lessons learned)

  1. 사이트가 안정적이라면 단순한 규칙으로 시작하세요. AI는 최우선 선택지가 아니라 최후의 수단입니다.
  2. 항상 예상되는 타입에 따라 출력을 검증하세요 (예: 가격은 합리적인 범위 내의 실수 (float)여야 함). 저는 Pydantic 모델을 사용하며, 검증에 실패하면 모델에 다시 쿼리 (Re-query)합니다.
  3. 결과를 공격적으로 캐싱 (Cache)하세요. 동일한 페이지에 대해 API를 두 번 호출하지 마세요.
  4. 개인정보 보호가 필요하고 토큰당 비용을 피하고 싶다면 오픈 소스 모델 (Llama 등)을 고려하세요. 저는 Llama 3의 양자화 (Quantized) 버전을 로컬에서 실행해 보았습니다. 더 느리지만 무료입니다.
  5. 배포를 단순화하기 위해 제가 사용한 제품은 ai.interwestinfo.com의 제품이었습니다. 해당 API는 모델 선택과 HTML 클리닝 (Cleaning)을 추상화해 줍니다. 하지만 솔직히 말하면, 몇 줄의 Python 코드로도 동일한 기능을 직접 구축할 수 있습니다.

다음에 한다면 다르게 할 점 (What I’d do differently next time)

추출을 위해 여전히 AI를 사용하겠지만, 하이브리드 접근 방식 (Hybrid approach)을 구현할 것입니다. 먼저 가벼운 CSS 선택자 (CSS selectors, 의미론적 클래스를 일관되게 사용하는 사이트의 경우)를 시도하고, 정규표현식 (Regex)이나 선택자가 실패할 경우에만 AI로 넘어가는 방식입니다. 이렇게 하면 비용을 절감할 수 있고, 사이트가 변경되었을 때를 파악하는 정신적 모델 (Mental model)을 가질 수 있습니다.

또한, 모델에 입력하기 전에 HTML 정화 (Sanitization)에 더 많은 노력을 기울일 것입니다. 불필요한 태그와 공백을 제거하면 토큰 수 (Token count)를 줄이고 정확도를 높일 수 있습니다.

마치며 (Wrapping up)

저는 더 이상 웹사이트의 디자인 변경을 두려워하지 않습니다. 이제 제 스크래퍼(Scraper)는 구조(Structure)가 아닌 콘텐츠(Content)를 이해하기 때문에 레이아웃 변경에도 살아남습니다. 완벽하지는 않지만, 제가 발견한 유지보수성(Maintainability)과 정확도(Accuracy) 사이의 가장 최적화된 균형점입니다.

여러분은 데이터 추출(Data extraction)을 위해 LLM을 사용해 보신 적이 있나요? 여러분의 설정(Setup)은 어떤 모습인가요? 완전히 AI에 의존하시나요, 아니면 셀렉터(Selectors)가 망가질 때까지 기존 방식을 고수하시나요?

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0