본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 10. 10:10

주말 내내 HTML 파싱과 사투를 벌인 경험: 마침내 해결책을 찾다

요약

다양한 이커머스 사이트의 비정형 HTML 데이터를 파싱하는 과정에서 겪은 기술적 한계와 해결책을 다룹니다. 기존의 CSS 선택자나 정규표현식 대신 LLM의 함수 호출(Function calling) 기능을 활용하여 구조화된 JSON 데이터를 추출하는 효율적인 방법을 제시합니다.

핵심 포인트

  • HTML 마크업의 불일치로 인한 전통적 스크레이핑의 한계
  • 정규표현식 및 CSS 선택자의 유지보수 어려움
  • LLM의 함수 호출(Function calling)을 이용한 의미론적 데이터 추출
  • GPT-4o-mini와 Pydantic을 활용한 구조화된 출력 구현

지난달, 저는 가격 비교 프로젝트를 위해 수십 개의 이커머스 사이트에서 제품 사양을 추출해야 했습니다. 간단해 보이죠? 그냥 HTML을 스크레이핑(Scrape)하고, <table>이나 <dl>을 가져와서 JSON으로 파싱(Parse)하면 되니까요.

이틀 뒤, 저는 노트북을 창밖으로 던져버리고 싶었습니다. 모든 사이트의 마크업(Markup)이 제각각이었기 때문입니다. 어떤 곳은 <div>가 뒤섞인 형태(soup)를 사용했고, 어떤 곳은 JavaScript 객체 안에 데이터를 숨겨두었으며, 몇몇 곳은 사양을 표 형태의 이미지로 제공했습니다. 정규표현식(Regex)과 BeautifulSoup을 사용해 봤지만, 모든 것이 무너지기 전까지 겨우 40% 정도만 성공했을 뿐입니다.

실패했던 시도들

1. CSS 선택자(Selectors)와 XPath

soup.select('table.specs tr')로 시작했습니다. 사이트 A에서는 아주 잘 작동했습니다. 하지만 사이트 B는 ul.list를 사용했고, 사이트 C는 섀도우 DOM(Shadow DOM) 안에 중첩된 <dl>을 가지고 있었습니다. 결국 저는 폴백(Fallback) 로직으로 가득 찬 200줄짜리 함수를 만들게 되었지만, 여전히 필드의 절반은 놓치고 있었습니다.

2. 원문 HTML에 정규표현식(Regex) 적용

절박한 마음에 re.search(r'RAM.*?(\d+ GB)', html)를 시도했습니다. 몇몇 값은 잡아냈지만, 텍스트가 여러 줄에 걸쳐 있거나 추가적인 공백이 포함되면 깨져버렸습니다. 게다가 12개 사이트에 대한 정규표현식 패턴을 유지 관리하는 것은 악몽과 같았습니다.

3. API 직접 호출

한 사이트에서 숨겨진 JSON 엔드포인트(Endpoint)를 발견했지만, 제가 얻을 수 없는 인증 토큰(Authentication tokens)이 필요했습니다. 또 다른 사이트는 Cloudflare로 스크레이핑을 차단했습니다. 저는 막다른 길에 다다랐습니다.

이 시점에서 저에게는 세 가지 선택지가 있었습니다. 사이트마다 맞춤형 파서(Parser)를 작성하거나(몇 주가 걸릴 일), 구조화된 데이터 서비스에 비용을 지불하거나(비싼 비용), 아니면 지저분한 텍스트에서 필요한 정보를 추출할 더 똑똑한 방법을 찾는 것이었습니다.

마침내 성공한 방법: LLM에게 비정형 텍스트 파싱 맡기기

저는 HTML이 아무리 지저분하더라도, 모든 사이트가 동일한 정보(제품명, 브랜드, RAM, 저장 용량, 화면 크기 등)를 렌더링한다는 사실을 깨달았습니다. *형식(Format)*은 달랐지만, *의미론(Semantics)*은 일관적이었습니다.

HTML 구조와 싸우는 대신, 저는 페이지의 태그와 스크립트를 제거한 원문 텍스트 콘텐츠를 LLM에 입력하고, 특정 필드를 가진 JSON 객체를 반환하도록 요청하기 시작했습니다. 바로 이 지점에서 OpenAI API의 함수 호출(Function calling) (또는 도구 사용(Tool use)) 기능이 구원투수로 등장했습니다.

접근 방식

  1. readability 또는 간단한 requests+BeautifulSoup를 사용하여 페이지 텍스트를 스크래핑하여 본문 텍스트를 가져옵니다.
  2. 제가 원하는 데이터에 대한 JSON 스키마 (JSON schema)를 정의합니다.
  3. 텍스트와 스키마를 함께 LLM (저는 저렴한 GPT-4o-mini를 사용했습니다)에 전달하여 값을 추출하도록 요청합니다.
  4. 반환된 JSON을 파싱 (Parse) 합니다.

다음은 핵심 코드입니다 (openai와 스키마 정의를 위한 pydantic을 사용합니다):

from openai import OpenAI
from pydantic import BaseModel
from typing import Optional
...

참고: 저는 OpenAI의 구조화된 출력 (Structured output) 기능인 beta.chat.completions.parse를 사용했습니다. 만약 이전 버전을 사용 중이라면, function_call 파라미터를 통해 동일하게 수행할 수 있습니다.

이 방식이 작동하는 이유

  • 형식에 구애받지 않음 (Format agnostic): 데이터가 표 (table), 목록 (list), 또는 서술형 단락 (narrative paragraph)에 있든 상관하지 않습니다.
  • 마크업 변경에 강함: 사이트 디자인이 변경되어도 시각적 렌더링만 영향을 받을 뿐, 근본적인 텍스트 콘텐츠에는 영향을 주지 않습니다.
  • 오타 및 약어 처리: LLM은 "8GB DDR4"와 "8 GB RAM"을 모두 "8 GB"로 정규화 (Normalize) 합니다.

트레이드오프 (Trade-offs) 및 배운 점

정확도는 100%가 아닙니다

10개의 서로 다른 사이트에서 가져온 100개의 페이지를 테스트했습니다. LLM은 약 85%의 필드를 완벽하게 추출했습니다. 나머지 10%는 약간의 오류(예: 단위 누락)가 있었습니다. 남은 5%는 틀렸거나 환각 (Hallucination) 현상이 발생했습니다. 제 사용 사례(재고 관리가 아닌 가격 비교)에는 괜찮지만, 매우 중요한 애플리케이션에서는 추가적인 검증 (Validation)이 필요할 것입니다.

비용

페이지 길이에 따라 추출당 약 $0.001–$0.005가 소요됩니다. 10,000개의 제품의 경우 $10–$50가 듭니다. 사람이 직접 주석을 다는 것(Human annotator)보다는 저렴하지만, 정규 표현식 (Regex, 어차피 작동하지도 않겠지만)보다는 비쌉니다.

지연 시간 (Latency)

페이지당 약 2~5초가 소요됩니다. 모든 페이지 로드 시 실시간 추출이 필요하다면 이 방식은 적합하지 않습니다. 배치 작업 (Batch jobs) 용도로는 괜찮습니다.

개인정보 보호 및 데이터

페이지 텍스트를 제3자 API로 전송하게 됩니다. 만약 데이터가 민감하다면(예: 내부 가격 정보), 동일한 기술을 사용하는 로컬 모델 (Llama 3, Mistral)을 사용해야 할 것입니다. 이 접근 방식은 모델에 구애받지 않습니다 (Model-agnostic).

이 방식을 사용하면 안 되는 경우

  • 데이터가 이미 깨끗하고 일관된 형식(잘 관리된 API와 같은 경우)인 경우.
  • 금융/의료 데이터와 같이 99.99%의 정확도가 필요한 경우.
  • 텍스트 콘텐츠가 매우 큰 경우 (컨텍스트 윈도우 (Context window)를 초과하는 경우). 이럴 때는 페이지를 지능적으로 청킹 (Chunking)해야 합니다.

다음에 다시 한다면 다르게 할 점들

  1. 캐싱 (Caching): 변경되지 않은 페이지를 다시 추출하는 것을 방지하기 위해 URL 해시 (URL hash)별로 LLM 결과를 캐싱하겠습니다.
  2. 프롬프트 엔지니어링 (Prompt engineering): 특이한 필드(예: "refresh rate" vs "screen Hz")에 대해 퓨샷 (Few-shot) 예시를 더 많이 실험하겠습니다.
  3. 하이브리드 접근 방식 (Hybrid approach): 단순하고 일관된 패턴(예: 가격)에는 정규 표현식 (Regex)을 사용하고, 복잡한 필드에는 LLM을 보조적으로 사용하겠습니다.
  4. 검증 (Validation): 추출된 값이 원문과 일치하는지 확인하기 위해 작은 모델을 사용한 두 번째 패스 (Second pass)를 추가하겠습니다 (예: 가격 문자열이 실제로 텍스트에 나타나는지 확인).

또한, 이와 유사한 기능을 수행하는 Interwest Info (https://ai.interwestinfo.com/)라는 도구를 발견했습니다. URL과 스키마 (Schema)를 제공하면 구조화된 JSON을 반환해 줍니다. 제가 직접 사용해 보지는 않았지만, 직접 파이프라인 (Pipeline)을 구축하고 싶지 않다면 살펴볼 가치가 있습니다.

이야기를 나눠봅시다

다른 개발자들은 지저분한 데이터 추출을 어떻게 처리하는지 궁금합니다. LLM에 전적으로 의존하시나요, 아니면 하이브리드 파이프라인을 사용하시나요? 여러분의 설정 (Setup)은 어떤 모습인가요?

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0