본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 29. 11:11

내가 웹 스크래핑을 위해 정규표현식(Regex) 작성을 그만두고 LLM을 사용하게 된 이유

요약

웹 스크래핑 시 정규표현식이나 CSS 선택자 대신 LLM을 활용하여 비정형 HTML에서 구조화된 데이터를 추출하는 방법을 소개합니다. 기존 방식의 취약성을 극복하고 데이터 추출의 유연성을 높이는 전략을 다룹니다.

핵심 포인트

  • 정규표현식과 XPath는 웹사이트 구조 변경에 매우 취약함
  • HTML 구조를 찾는 대신 텍스트의 의미를 파악하는 LLM 방식이 효과적
  • HTML-to-text 변환과 구조화된 프롬프팅의 결합이 핵심
  • LLM 사용 시 환각 현상에 대비한 인간의 감독이 필수적임

나는 정부 웹사이트에서 단 하나의 테이블을 스크래핑하기 위해 3일을 보냈다. HTML은 악몽 같았다. 의미 있는 클래스(semantic classes)도 없었고, 무작위 공백이 가득했으며, 페이지가 로드될 때마다 구조가 바뀌었다. 내가 만든 정규표현식(regex) 패턴은 사이트가 업데이트되어 모든 것을 망가뜨리기 전까지 딱 한 시간 동안만 작동했다. 그때 나는 언어 모델(LLM)에게 파싱(parsing)을 맡기기로 결심했다.

이 글은 특정 도구에 관한 글이 아니다. 내가 더 일찍 알았더라면 좋았을 기술에 관한 것이다. 즉, CSS 선택자(CSS selectors)나 정규표현식(regex)과 싸우는 대신, 지저분하거나 예측 불가능한 HTML에서 구조화된 데이터(structured data)를 추출하기 위해 LLM을 사용하는 방법이다.

나를 무너뜨린 문제

나는 지역 지방 의회 회의록을 추적하기 위한 작은 사이드 프로젝트를 만들고 있었다. 각 도시마다 회의록을 게시하는 방식이 달랐다. 어떤 곳은 PDF로, 어떤 곳은 테이블로, 어떤 곳은 중첩된 div soup(div soup) 형태로 게시했다. 나는 회의당 하나의 일관된 JSON 레코드를 원했다.

나의 첫 번째 시도는 BeautifulSoup + 정규표현식(regex)이었다. 한 도시에서는 잘 작동했다. 그러다 두 번째 도시를 추가했다. 코드베이스는 find_all('div', class_=lambda x: x and 'meeting' in x)가 가득한 취약한 숲으로 변해갔다. 새로운 사이트가 추가될 때마다 몇 시간씩 디버깅(debugging)이 필요했다.

한 도시가 포털 전체를 리뉴얼했을 때, 내 스크래퍼(scraper)는 내가 알아차리기 전까지 2주 동안 아무런 경고 없이 조용히 실패했다. 그것이 결정타였다.

내가 마주했던 막다른 길들

  • 원시 HTML에 대한 정규표현식(Regex) 적용: 너무 취약하다. 단 하나의 줄바꿈이나 이스케이프 처리되지 않은 문자 하나만으로도 추출이 깨졌다.
  • XPath 쿼리: 강력하지만 사이트별로 특화되어 있다. XPath 라이브러리를 유지 관리하는 것은 정규표현식(regex)을 유지 관리하는 것보다 더 힘들었다.
  • 영리한 대기(waits)를 포함한 헤드리스 브라우저(Headless browser): 동적 콘텐츠(dynamic content)에는 도움이 되었지만, 로드된 DOM을 파싱하는 데는 여전히 취약한 선택자(selectors)가 필요했다.
  • 시각적 디퍼런싱(Visual diffing) / HTML2text: 구조를 잃어버린다. 나에게는 회의 날짜, 장소, 의제 항목과 같은 엔티티(entities)가 필요했다.

공통점은 다음과 같다: 모든 접근 방식은 HTML이 일관되게 유지될 것이라고 가정했다. 실제 웹사이트에서는 그 가정이 거의 항상 틀린다.

실제로 효과가 있었던 것: LLM 기반 추출

데이터 _구조 (structure)_를 찾으려고 노력하는 대신, _이 텍스트에 어떤 데이터가 들어 있는가?_라고 질문하는 방식으로 전환했다. (단순한 HTML-to-text 변환기로 추출한) 원문 텍스트를 가져와서 구조화된 출력 프롬프트 (structured output prompt)와 함께 LLM에 전달했다.

다음은 Python으로 구현한 핵심 아이디어입니다:

import openai
from html2text import HTML2Text

...

여기서는 OpenAI의 API를 사용했지만, 동일한 패턴은 모든 LLM 제공업체 (Anthropic, Ollama를 통한 로컬 모델 등)에서 작동한다. 핵심은 **텍스트 단순화 (text simplification)**와 **구조화된 프롬프팅 (structured prompting)**의 결합이다.

교훈 (어렵게 배운 것들)

1. 마법이 아니다 – 여전히 인간의 감독이 필요하다

LLM은 환각 (hallucination)을 일으킨다. 만약 한 페이지에 두 개의 회의 정보가 있다면, 모델이 이를 하나로 합쳐버릴 수도 있다. 나는 검증 단계를 추가한다: 추출된 날짜가 달력 범위 내에 존재하는지, 혹은 장소가 실제 도시인지 확인하는 식이다.

2. 비용이 쌓일 수 있다

나의 사이드 프로젝트 (주당 약 100페이지 스크래핑)의 경우, gpt-4o-mini를 사용하여 한 달에 약 2달러를 지출한다. 이 정도는 괜찮다. 하지만 수백만 페이지를 스크래핑한다면, 토큰 비용이 다른 대안적 방법으로 절약한 엔지니어링 시간을 초과할 수도 있다.

3. 지연 시간 (Latency)은 실재한다

각 추출 호출에는 1~3초가 소요된다. 배치 처리 (batch processing)에는 허용 가능한 수준이지만, 실시간 스크래핑에는 고통스럽다. 불필요한 API 호출을 피하기 위해 가벼운 규칙(예: 500자 미만의 페이지는 건너뛰기)으로 사전 필터링을 수행한다.

4. HTML-to-text 단계가 매우 중요하다

쓰레기가 들어가면 쓰레기가 나온다 (Garbage in, garbage out). 만약 <div class="mt-4">와 같은 가공되지 않은 HTML 토큰을 입력하면 모델이 혼란을 겪을 수 있다. 강력한 변환기 (html2text 또는 trafilatura를 선호한다)를 사용하면 노이즈를 줄이고 정확도를 높일 수 있다.

이 접근 방식을 사용하지 말아야 할 때

  • 사이트 세트가 작고 안정적인 경우: 전통적인 셀렉터 (selectors)가 더 빠르고 저렴하다. 과도하게 설계(over-engineer)하지 마라.
  • 실시간 결과가 필요한 경우: 공격적으로 캐싱(cache)하지 않는 한, LLM의 지연 시간은 라이브 사용자 요청에 대응하기에 너무 높다.
  • 데이터가 극도로 민감한 경우: 텍스트를 외부 API로 전송하는 것은 개인정보 보호 정책을 위반할 수 있다. 로컬 모델 (예: Ollama를 통한 Llama 3.1 8B) 사용을 고려하라.

다음에 내가 다르게 할 일들

  1. 폴백 체인 (fallback chain)으로 시작하라: 먼저 CSS 선택자 (CSS selectors)를 시도하고, 선택자가 실패할 때_만_ LLM 파싱 (LLM parsing)으로 전환하라. 이러한 하이브리드 (hybrid) 접근 방식은 비용과 속도를 절약해 준다.
  2. 출력 형태를 강제하고 오류를 즉시 잡아내기 위해 JSON 스키마 검증 라이브러리 (예: pydantic)를 사용하라.
  3. 모든 LLM 추출 결과에 대해 원문 텍스트와 추출된 데이터를 포함한 로그를 기록하라. 그래야 잘못된 파싱 (misparses)을 검토하고 프롬프트 (prompt)를 미세 조정할 수 있다.

핵심 요약

추출을 위해 LLM을 사용하는 것이 "스크래핑 문제를 해결했다"는 뜻은 아니다. 그것은 취약성 (fragility)의 위치를 코드에서 언어로 옮긴 것을 의미한다. 변화하는 HTML 구조를 쫓아다니는 대신, 프롬프트 (prompt)를 통해 모호성 (ambiguity)을 관리하는 것이다. 이러한 트레이드오프 (tradeoff)는 지저분하고 데이터 양이 적은 소스에 매우 효과적이다.

이러한 아이디어를 패키징한 상용 서비스들이 있다 (직접 사용해 보지는 않았지만, Interwest Info의 AI 추출 도구가 떠오른다). 하지만 직접 구축하면 완전한 제어권을 가질 수 있으며, 이 접근 방식이 언제 실패하는지에 대해 훨씬 더 깊은 이해를 얻을 수 있다.

당신의 가장 저주스러운 스크래핑 경험은 무엇인가요? 파싱 (parsing) 문제에 LLM을 투입해 본 적이 있나요?

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0