정규 표현식(Regex)이 실패할 때: 지저분한 페이지에서 구조화된 데이터를 추출하기 위해 LLM 사용하기
요약
정규 표현식과 전통적인 웹 스크레이핑 방식이 복잡한 HTML 구조에서 실패할 때, LLM을 활용하여 구조화된 데이터를 추출하는 방법을 소개합니다. Few-shot prompting과 JSON 출력 형식을 사용하여 다양한 레이아웃에서도 안정적으로 데이터를 파싱하는 워크플로우를 제안합니다.
핵심 포인트
- 전통적인 셀렉터와 Regex의 한계 극복
- LLM을 활용한 범용 HTML 파서 구축
- Few-shot prompting을 통한 추출 정확도 향상
- 토큰 절약을 위한 HTML 조각 추출 전략
저는 수년 동안 웹 스크레이핑 (web scraping)을 해왔습니다. 대부분의 프로젝트에서 저는 BeautifulSoup, cssselect, 그리고 몇 가지 정규 표현식 (regex) 패턴에 의존합니다. 여러분도 잘 아는 방식이죠. 페이지를 검사하고, 셀렉터 (selector)를 찾고, 텍스트를 추출하고, 정리하는 과정 말입니다. 모든 페이지가 동일한 템플릿을 따를 때는 아주 잘 작동합니다.
그러다 수백 개의 작은 이커머스 사이트에서 제품 상세 정보를 스크레이핑해야 하는 프로젝트를 맡게 되었습니다. 사이트마다 HTML 구조가 제각각이었습니다. 어떤 곳은 <div class="price">를 사용했고, 다른 곳은 <span itemprop="price">를 사용했으며, 몇몇 곳은 클래스도 없이 단락 속에 $29.99가 그냥 묻혀 있었습니다. 제가 정성껏 만든 셀렉터들은 불과 12개 정도의 사이트를 지나자마자 깨져버렸습니다. 데이터를 실제로 사용하는 시간보다 조건부 파서 (conditional parsers)를 작성하는 데 더 많은 시간을 쓰고 있었습니다.
시도했지만 실패했던 방법들
저의 첫 번째 본능은 문제에 더 많은 코드를 쏟아붓는 것이었습니다. 저는 여러 셀렉터를 시도하고 가격과 같은 패턴에는 정규 표현식 (regex)으로 대체하는 메타 파서 (meta-parser)를 작성했습니다. 그것은 작동했습니다... 어떤 사이트가 다른 통화 기호를 사용하거나, 원래 가격 뒤에 할인 가격이 나타나기 전까지는 말이죠. 디버깅 (debugging)은 악몽이 되었습니다.
다음으로, 속성과 주변 텍스트를 기반으로 요소(가격, 이름, 설명)를 태깅하는 간단한 분류기 (classifier)를 학습시키는 것을 시도했습니다. 클래스 이름, 태그 이름, 텍스트 길이와 같은 특징 (features)을 사용하여 scikit-learn을 사용했습니다. 학습 데이터셋 (training set)에서는 괜찮게 작동했지만, 새로운 레이아웃에서는 실패했습니다. 특징 공간 (feature space)이 너무 얕았습니다.
또한 계산된 스타일 (computed styles)을 가져오기 위해 헤드리스 브라우저 (headless browsers)를 실험하며, 굵기나 색상을 통해 가격을 식별할 수 있기를 희망하기도 했습니다. 하지만 그것은 취약하고 느렸습니다.
마침내 성공한 접근 방식: 범용 파서로서의 LLM
몇 주간의 좌절 끝에, 저는 미친 생각을 하나 했습니다. 만약 제가 그냥 원시 HTML (raw HTML)을 언어 모델 (language model)에 보내고 제가 필요한 데이터를 추출해 달라고 요청하면 어떨까? 셀렉터도, 정규 표현식 (regex)도 없이—그저 프롬프트 (prompt)만으로 말이죠.
제품 페이지의 작은 HTML 조각으로 시도해 보았고, 성공했습니다. 완벽하지는 않았지만, 대부분의 경우 올바른 값을 가져왔습니다. 핵심은 몇 가지 예시를 제공하는 것 (few-shot prompting)과 명확한 출력 형식 (output format)을 지정하는 것이었습니다.
핵심 기술은 다음과 같습니다:
- 제품 주변의 페이지 영역을 작게 추출합니다 (토큰 수 (token counts)를 낮게 유지하기 위해).
- HTML → JSON 변환 예시를 2~3개 포함하는 프롬프트 (prompt)를 작성합니다.
- 대상 HTML을 전송하고 JSON 응답을 파싱 (parse)합니다.
코드 예시 (Python + OpenAI 사용)
import openai
import json
...
이 코드 조각이 핵심입니다. 리뷰, 사양 (specifications), 심지어 테이블 행 (table rows) 등 어떤 구조화된 데이터 (structured data)에도 맞게 조정할 수 있습니다.
교훈 및 트레이드오프 (Trade-offs)
이 접근 방식이 만능 해결책 (silver bullet)은 아닙니다. 제가 발견한 점들은 다음과 같습니다:
- 비용 (Cost): 모든 추출에는 토큰 (tokens)이 소모됩니다. 대량 스크래핑 (high-volume scraping, 수천 개의 제품)의 경우, LLM API 비용이 쌓일 수 있습니다. GPT-3.5를 사용할 경우 추출당 $0.02–$0.05 정도로 예상합니다. 1만 개의 제품이 있는 프로젝트라면 $200–500가 됩니다. 규모가 작은 작업에서는 무시할 만한 수준입니다.
- 지연 시간 (Latency): 각 요청은 1~3초가 소요됩니다. 실시간 스크래핑 (real-time scraping)에는 적합하지 않지만, 배치 처리 (batch processing)에는 괜찮습니다.
- 부정확성 (Inaccuracies): LLM은 환각 (hallucinate) 현상을 일으킵니다. 때로는 가격이 몇 센트 차이가 나거나, 재고 유무를 추측하기도 합니다. 저는 추출된 가격이 숫자에 대한 정규 표현식 (regex) 패턴과 일치하는지 확인하는 검증 단계를 추가했습니다. 일치하지 않으면 수동 검토를 위해 플래그 (flag)를 지정합니다.
- 토큰 제한 (Token limits): 페이지의 HTML 전체를 보낼 수는 없습니다. 제품 주변의 영역을 미리 다듬어야 (pre-trim) 합니다. 저는 간단한 휴리스틱 (heuristic)을 사용했습니다. 가격과 유사한 패턴을 포함하는 첫 번째
<article>또는<div>를 찾은 다음, 해당 서브트리 (subtree)를 전송하는 방식입니다. - 개인정보 보호 (Privacy): 내부 데이터를 스크래핑하는 경우, HTML을 제3자 API로 전송하는 것이 허용되지 않을 수 있습니다. Ollama 또는 llama.cpp를 통해 Llama 3 또는 Mistral과 같은 로컬 모델 (local models)을 사용하세요. 이들은 더 느리고 정확도가 낮지만, 데이터를 온프레미스 (on-prem)에 유지할 수 있습니다.
사용하지 말아야 할 때
대상 페이지의 구조가 일관적이라면 CSS 선택자 (CSS selectors)를 고수하세요. 비용이 들지 않고, 빠르며, 결정론적 (deterministic)입니다. LLM 추출은 지저분하고 예측 불가능한 페이지들의 롱테일 (long tail)을 위한 것입니다. 저는 이제 하이브리드 (hybrid) 방식을 사용합니다. 먼저 알려진 사이트에 대해서는 전통적인 파서 (parser)를 시도하고, 알 수 없는 레이아웃의 경우 LLM으로 전환합니다.
다음에 한다면 다르게 할 점
폴백 전략 (fallback strategy)에 더 많은 시간을 투자할 것입니다. HTML 영역 전체를 보내는 대신, 노이즈(scripts, styles)를 제거하고 속성 이름 (attribute names)을 정규화하는 전처리 (pre-process) 과정을 거칠 수 있습니다. 그렇게 하면 토큰 (tokens) 사용량을 줄이고 정확도를 높일 수 있을 것입니다. 또한 더 저렴한 모델인 GPT-3.5-turbo-instruct 등을 사용하여 성공률을 비교해 볼 것입니다.
또 다른 개선 사항은 파싱 오류 (parsing errors)를 방지하기 위해 구조화된 출력 형식 (structured output format, 최근 모델의 JSON mode와 같은 방식)을 사용하는 것입니다. 일부 API는 이제 JSON 스키마 (JSON schema)를 지정할 수 있게 해주는데, 이를 활용하면 수동 검증 (manual validation)의 필요성을 없앨 수 있습니다.
저는 다시는 수십 개의 취약한 파서 (parsers)를 작성하는 방식으로 돌아가지 않을 것입니다. 자연어로 "데이터만 추출해줘"라고 말할 수 있는 능력은 마치 치팅 (cheating)처럼 느껴지지만, 실제로 작동합니다. 트레이드오프 (trade-offs)는 분명히 존재하지만, 제가 수행하는 지저분하고 일회성인 스크래핑 (scraping) 작업들의 종류를 고려할 때, 이 기술은 몇 주간의 노력을 아껴주었습니다.
데이터 추출을 위해 LLM을 사용해 보신 적이 있나요? 여러분의 경험은 어떠했나요? 저와 같은 함정 (pitfalls)에 빠지셨나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기