정규 표현식(Regex)만으로는 부족할 때: LLM을 활용한 구조화된 데이터 추출
요약
정규 표현식과 CSS 선택자만으로는 해결하기 어려운 복잡하고 다양한 이커머스 데이터 스크레이핑 문제를 LLM을 활용해 해결하는 방법을 소개합니다. GPT-4의 함수 호출(Function calling) 기능을 사용하여 비정형 HTML 데이터로부터 원하는 스키마에 맞춘 구조화된 JSON 데이터를 추출하는 실무적인 접근법을 다룹니다.
핵심 포인트
- 정규 표현식 기반 스크레이핑의 확장성 한계 인지
- LLM의 함수 호출(Function calling)을 통한 구조화된 데이터 추출
- Pydantic을 활용한 데이터 스키마 정의 및 검증
- 비정형 텍스트에서 정확한 JSON 객체 반환 가능
지난달 저는 한계에 부딪혔습니다. 한 고객이 50개의 서로 다른 이커머스 사이트에서 제품 상세 정보(가격, 사이즈, 색상, 설명 등)를 스크레이핑(Scraping)해 달라고 요청했습니다. 이전에도 스크레이핑을 해본 적이 있었기에, 저는 자신 있게 BeautifulSoup, CSS 선택자(CSS selectors), 가격 형식을 위한 정규 표현식(Regex) 패턴을 사용하여 작업을 시작했습니다.
두 시간 뒤, 저는 엉망진창이 된 결과물을 바라보고 있었습니다. 어떤 사이트는 가격을 $19.99로 표시했고, 다른 사이트는 19,99 €로 표시했습니다. 사이즈는 어떤 페이지에서는 <select> 드롭다운이었고, 다른 페이지에서는 라디오 버튼이었으며, 세 번째 페이지에서는 자유 형식의 텍스트였습니다. 모든 사이트마다 각기 다른 특이점이 있었습니다. 하루가 끝날 무렵, 저는 각 도메인마다 깨지기 쉬운 정규 표현식들을 만들어 놓은 상태였고, 고객은 다음 주에 10개의 사이트를 더 추가하고 싶어 했습니다. 저는 이것이 확장 가능하지(Scale) 않다는 것을 알고 있었습니다.
제가 시도했던 것들 (그리고 왜 힘들었는지)
먼저, 저는 패턴에 더 집중했습니다. £19.99, 19.99 GBP, $19.99 – $29.99 등을 포착하기 위해 더 공격적인 정규 표현식을 작성했습니다. 이는 결국 선택 사항(Alternations)과 이름이 지정된 그룹(Named groups)이 얽힌 복잡한 구조가 되었습니다. 저는 인라인으로 할인 배지가 포함된 가격(was $29.99, now $19.99)과 같이 놓친 예외 케이스들을 머릿속으로 계속 목록화해야 했습니다.
그다음에는 시각적 파싱(Visual parsing)을 시도했습니다. Playwright를 이용한 헤드리스 크롬(Headless Chrome)을 사용하여 DOM이 안정화되기를 기다린 다음, 트리를 탐색하며 가격처럼 보이는 텍스트 노드를 찾는 방식이었습니다. 이는 느렸고 여전히 취약했습니다. 사이트 디자인이 변경되면 모든 것이 망가질 상황이었습니다.
거의 포기하고 데이터 입력 요원을 고용할 뻔했습니다. 하지만 저는 제가 아직 보지 못한 사이트까지 포함하여 어떤 사이트든 처리할 수 있는 솔루션을 원했습니다.
마침내 효과를 본 접근 방식: 구조화된 LLM 출력
저는 코드 생성용으로 GPT-4를 사용해 왔지만, 데이터 추출에 사용하는 것은 고려하지 않았습니다. 아이디어는 간단했습니다. LLM에 가공되지 않은 HTML(또는 정제된 텍스트 버전)과 제가 원하는 스키마(Schema)를 제공하고, JSON으로 출력하도록 요청하는 것이었습니다.
핵심은 함수 호출(Function calling) (또는 도구 사용(Tool use))이었습니다. 모델에게 자유 형식의 JSON을 작성하라고 요청하는 대신, 제가 원하는 정확한 필드들을 타입(Type) 및 설명과 함께 함수로 정의했습니다. 그러면 모델은 제가 프로그래밍 방식으로 검증할 수 있는 구조화된 객체를 반환합니다.
Python에서 이를 설정하는 방법은 다음과 같습니다:
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import List, Optional
...
저는 이를 무작위 신발 쇼핑몰 페이지에서 테스트했습니다. 첫 번째 호출 결과로 name="Running Sneaker X", price="$89.99", colors=["Black","White"]가 반환되었습니다. 완벽했습니다. 마크업(Markup)과 문구가 서로 다른 세 개의 사이트에서 추가로 테스트해 보았는데, 매번 정확하게 추출해 냈습니다.
현실적인 점검: 이것은 마법이 아닙니다
이 접근 방식은 제 프로젝트를 살렸지만, 실제적인 트레이드오프 (Trade-offs)가 존재합니다:
- 비용 (Cost): 추출할 때마다 약간의 토큰 (Tokens)이 소모됩니다. 수백 페이지 정도라면 저렴합니다 (아마 몇 센트 정도일 것입니다). 하지만 수백만 개의 제품을 스크래핑한다면? 비용이 빠르게 불어납니다.
- 지연 시간 (Latency): 각 호출에는 1~3초가 소요됩니다. 배치 (Batch) 처리나 병렬화를 할 수는 있지만, 마이크로초 단위로 실행되는 정규 표현식 (Regex) 기반 솔루션보다는 여전히 느립니다.
- 환각 (Hallucinations): 페이지에 가격이 보이지 않는 경우 모델이 가격을 지어낼 수도 있습니다. 저는 엄격한 스키마 검증 (Schema validation)을 통해 이 문제를 해결했습니다. 필수 필드가 누락되었거나 쓰레기 데이터가 포함된 경우, 더 구체적인 프롬프트 (Prompt)와 함께 다시 실행합니다.
- 프롬프트 민감도 (Prompt sensitivity): 명확한 지침을 주지 않으면 모델이 "$19.99" 대신 "가격은 ...입니다"와 같은 추가 텍스트를 포함할 수 있습니다. 시스템 프롬프트 (System prompt)를 반복적으로 개선해야 합니다.
이러한 단점에도 불구하고, 동적이고 이질적인 콘텐츠를 다룰 때 이는 제가 발견한 최고의 도구입니다. 저는 결국 이 로직을 작은 내부 서비스로 래핑 (Wrapping)했습니다. 본질적으로 ai.interwestinfo.com의 추출 API와 유사한 패턴이지만, 저만의 스키마 정의와 재시도 로직 (Retry logic)을 갖춘 형태입니다.
배운 점들
- AI를 사용할 수 있다면 모든 사이트에 대해 정규 표현식을 만들지 마세요. 설정이 더 빠르고 레이아웃 변경에 더 탄력적입니다.
- 항상 출력을 검증하세요. Pydantic의
model_validate는 타입 오류와 누락된 필드를 즉시 잡아냅니다. - 먼저 HTML을 정제하세요. 전체 HTML은 토큰을 낭비합니다. 가독성 라이브러리 (Readability library)를 사용하여 주요 콘텐츠 블록만 가져오세요.
- 피드백을 포함한 재시도 루프 (Retry loop)를 유지하세요. 검증에 실패하면 에러 메시지를 모델에 다시 보내고 출력을 수정하도록 요청하세요.
이 방식을 사용하지 말아야 할 때
구조가 안정적인 단일 소스를 스크레이핑(Scraping)하는 경우라면, 특정 CSS 선택자(Selector)를 작성하기만 하면 됩니다. 만약 수백만 개의 페이지를 처리해야 하고 예산이 엄격하다면, 더 작은 모델을 학습시키거나 전통적인 파싱(Parsing) 방식을 사용하는 데 투자하세요. LLM은 지저분하고 구조화되지 않은 예외 케이스(Edge cases)를 위한 것입니다.
다음에 제가 다르게 시도할 점
저는 몇 개의 대표적인 페이지로 시작하여 테스트 스위트(Test suite)를 구축할 것입니다. 또한 지연 시간(Latency)과 비용을 줄이기 위해 gpt-4o-mini와 같은 더 저렴한 모델이나, 심지어 로컬 모델(예: 도구 호출(Tool calling) 기능이 있는 Llama 3)을 실험해 볼 것입니다. 그리고 스키마(Schema) 정의를 설정 파일(Config file)로 옮겨서, 개발자가 아닌 사람도 코드에 손을 대지 않고 이를 수정할 수 있도록 할 것입니다.
이 방법은 저의 악몽 같았던 프로젝트를 이틀짜리 작업으로 바꾸어 놓았습니다. 이제 데이터는 클라이언트의 데이터베이스로 흘러 들어가고 있으며, 사이트 디자인이 변경되더라도 모든 것이 망가지지 않을 것이라는 사실을 알기에 저는 더 편안하게 잠을 잡니다.
데이터 추출을 위해 LLM을 사용해 본 여러분의 경험은 어떠신가요? 구조화된 출력(Structured output)과 자유 형식 프롬프트(Free-form prompts) 중 어느 쪽이 더 효과적이었나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기