LLM을 사용하여 지저분한 텍스트에서 구조화된 데이터 추출하기 (그리고 regex가 실패한 이유)
요약
비정형 HTML 데이터에서 제품 정보를 추출할 때 정규표현식(regex)의 한계를 LLM의 함수 호출(function calling) 기능을 통해 극복하는 방법을 다룹니다. LLM을 활용하면 복잡한 HTML 구조에서도 문맥을 이해하여 높은 정확도로 구조화된 JSON 데이터를 생성할 수 있습니다.
핵심 포인트
- 정규표현식은 일관성 없는 HTML 구조와 오타가 있는 데이터 처리에 한계가 있음
- LLM의 함수 호출(function calling)을 통해 비정형 텍스트를 구조화된 JSON으로 변환 가능
- 테스트 결과 LLM 방식이 정규표현식 대비 데이터 추출 정확도가 크게 향상됨
- LLM의 환각 현상을 방지하기 위해 간단한 패턴 검증을 병행하는 것이 권장됨
저는 주말 동안 12개의 서로 다른 이커머스 사이트에서 제품 목록을 스크레이핑(scraping)하려고 시도했습니다. 목표는 간단했습니다. 이름, 가격, 재고 여부, 설명을 깔끔한 JSON 배열로 가져오는 것이었습니다. 제가 얻은 결과는 웹이 일관성 없는 HTML, 오타, 그리고 "창의적인" 서식들이 뒤섞인 아름다운 난장판이라는 사실을 뼈아프게 상기시켜 주는 것이었습니다.
지저분한 현실
저는 BeautifulSoup과 정규표현식 (regex)으로 시작했습니다. 각 사이트마다 고유한 특징이 있었습니다. 어떤 사이트는 가격을 <span class="price">로 감싸 놓았고, 다른 사이트는 data-price 속성을 사용했으며, 한 사이트는 클래스도 없는 <p> 태그 안에 그냥 "$19.99"라고 적어 놓았습니다. 저의 추출 로직은 중첩된 if-else 지옥으로 변해갔습니다:
import re
from bs4 import BeautifulSoup
...
이 방식은 아마 60% 정도의 케이스에서만 작동했습니다. 나머지는요? 잘못된 가격, 누락된 데이터, 또는 잘못된 양성 (false positives) 결과였습니다. 한 제품 설명에는 자리 표시자(placeholder)로 "Price: $0.00"이 포함되어 있었는데, 저의 regex가 이를 탐욕스럽게(greedily) 잡아냈습니다. 저는 더 나은 방법이 필요했습니다.
전통적인 파서(parsers)가 부족한 이유
근본적인 문제는 HTML 구조가 의미론적 (semantic)이지 않다는 것입니다. 두 사이트가 동일한 정보를 완전히 다른 방식으로 표시할 수 있습니다. 심지어 단일 사이트 내에서도 제품 카드는 약간의 변형이 있을 수 있습니다. 여기에는 추가적인 <div>가 있고, 저기에는 클래스가 누락되어 있습니다. 저의 파싱 로직은 취약했고 지속적인 유지보수가 필요했습니다.
텍스트 분류 (text classification)를 위해 머신러닝 (machine learning)을 사용하는 것을 고려했지만, 각 필드마다 커스텀 모델을 훈련시키는 것은 과해 보였습니다. 그때 저는 기억해 냈습니다. 대규모 언어 모델 (LLMs)은 정중하게 요청하기만 한다면 문맥을 이해하고 정보를 추출하는 데 매우 뛰어나다는 사실을 말입니다.
LLM 접근 방식
모든 가능한 HTML 구조에 대해 규칙을 작성하는 대신, 가공되지 않은 HTML (또는 더 나은 방법으로, 보이는 텍스트)을 LLM에 입력하고 제가 필요한 필드를 구조화된 형식으로 추출하도록 요청할 수 있었습니다. 핵심 기술은 함수 호출 (function calling) (또는 도구 사용 (tool use))입니다. 즉, LLM에게 특정 스키마 (schema)에 따라 JSON을 출력하도록 지시하는 것입니다.
저는 OpenAI의 GPT-4를 사용했지만, 동일한 패턴은 구조화된 출력 (structured output)을 지원하는 모든 모델(Claude, Gemini, Ollama를 통한 로컬 모델)에서 작동합니다. 제가 최종적으로 구현한 결과물은 다음과 같습니다:
from openai import OpenAI
import json
...
이 방식은 놀라울 정도로 잘 작동했습니다. HTML에 불필요한 내용이 포함되어 있거나 클래스 이름 (class names)이 약간 다르더라도, LLM은 의도를 파악해냈습니다. 서로 다른 사이트의 무작위 제품 페이지 50개를 대상으로 테스트한 결과, 정규표현식 (regex) 방식이 62%의 정확도를 보인 것에 비해 LLM은 84%의 사례에서 4개의 필드를 모두 정확하게 추출했습니다.
교훈 및 트레이드오프 (trade-offs)
정확도가 완벽하지는 않습니다. LLM은 때때로 가격을 환각 (hallucination)하거나 (예: 가격을 찾지 못했을 때 "$0.00"으로 출력), 재고 유무를 잘못 식별하기도 했습니다. 이를 해결하기 위해 간단한 패턴(예: 가격은 \$\d+\.\d{2}와 일치해야 함)을 사용하여 필드를 검증하는 후처리 (post-processing) 단계를 추가했습니다.
비용과 지연 시간 (latency). 추출할 때마다 소량의 토큰 비용이 발생합니다. 대량의 스크래핑 (scraping)을 수행할 경우 이 비용은 누적됩니다. 저는 HTML 입력을 4,000자(약 1,000 토큰)로 제한하고, 가능한 경우 요청을 배치 (batch) 처리했습니다. 평균적으로 제품 하나당 2~3초가 소요되었는데, 이는 수백 개의 제품에는 수용 가능한 수준이지만 수백만 개를 처리하기에는 적합하지 않습니다.
개인정보 보호 문제. 전체 HTML을 제3자 API로 전송한다는 것은 잠재적으로 민감한 데이터(고객 리뷰, 사용자 스크립트 등)를 공유한다는 의미입니다. 내부 도구용이라면, 동일한 함수 호출 (function calling) 패턴을 사용하여 Ollama를 통해 Llama 3와 같은 로컬 모델을 실행할 것입니다.
사용하지 말아야 할 때. 만약 HTML 구조가 완벽하게 일관적이라면 (예: 예측 가능한 양식이 있는 내부 관리자 페이지), 전통적인 파서 (parser)가 더 빠르고, 저렴하며, 더 신뢰할 수 있습니다. LLM은 소스가 매우 다양하거나 반구조화된 텍스트 (이메일, PDF, 채팅 로그)를 다룰 때 빛을 발합니다.
다음에 다시 한다면 다르게 할 점
- Pydantic 스키마 (Pydantic schema) 사용 – 가공되지 않은 JSON 대신 더 나은 검증을 위해 사용합니다.
- 가시적인 텍스트만 입력 – 토큰(token) 수와 노이즈를 줄이기 위해 먼저 모든 HTML 태그를 제거합니다.
- 결과 캐싱 (Cache results) – 동일한 페이지를 다시 스크래핑하는 경우, LLM 호출을 건너뜁니다.
- 더 작은 모델 시도 – GPT-3.5는 더 저렴했지만 정확도가 낮았습니다. 일부 필드의 경우, 미세 조정(fine-tuned)된 작은 모델이 효과적일 수 있습니다.
또한, 올바른 추출 사례를 몇 가지 앞에 추가하는 것(퓨샷 프롬프팅, few-shot prompting)이 할인이나 번들 가격과 같은 예외 케이스(edge cases)에 대한 정확도를 크게 향상시킨다는 것을 발견했습니다.
요점 (The takeaway)
LLM은 마법이 아니지만, 인간과 같은 이해가 필요한 추출 작업에서는 취약한 파싱(parsing) 방식보다 항상 우위에 있습니다. 제가 공유한 기술, 즉 함수 호출(function calls)을 사용하여 구조화된 JSON을 얻는 방식은 AI 커뮤니티에서 표준 패턴이 되어가고 있습니다. 이것이 만능 해결책(silver bullet)은 아니지만, 여러분의 도구 상자에서 매우 유용한 도구임은 분명합니다.
지저분한 데이터 추출을 위해 여러분이 주로 사용하는 방법은 무엇인가요? 정규표현식(Regex) 순수주의자인가요, 아니면 LLM으로 전향하셨나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기