본문으로 건너뛰기

© 2026 Molayo

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

내 웹 스크래퍼가 계속 고장 나는 이유와 마침내 해결한 방법

요약

다양한 이커머스 사이트의 HTML 구조 변화로 인해 발생하는 웹 스크래핑의 어려움을 LLM을 활용해 해결하는 방법을 소개합니다. 기존의 CSS 선택자나 머신러닝 방식 대신, HTML 스니펫을 언어 모델에 전달하여 구조화된 데이터를 추출하는 파이프라인을 제안합니다.

핵심 포인트

  • 전통적인 CSS/XPath 방식은 사이트 레이아웃 변경에 매우 취약함
  • OCR이나 단순 ML 분류기 방식은 운영 환경에서 정확도 유지의 한계가 있음
  • HTML 전체가 아닌 핵심 컨테이너 스니펫만 추출하여 LLM에 전달하는 것이 효율적
  • LLM을 활용하면 복잡한 규칙 없이도 구조화된 JSON 데이터를 안정적으로 얻을 수 있음

지난달, 저는 50개의 서로 다른 이커머스 사이트에서 제품 가격을 스크래핑해야 했습니다. 제가 평소 사용하던 도구들인 BeautifulSoup, CSS selectors, 그리고 약간의 XPath는 일회성 작업에서는 항상 잘 작동했습니다. 하지만 이번 프로젝트는 HTML 구조가 판이하게 다른 수십 개의 상점을 다뤄야 했습니다.

코딩을 시작했습니다. 하루 만에 세 개의 사이트에서 완벽하게 작동하는 스크래퍼를 만들었습니다. 이틀째에는 네 번째와 다섯 번째 사이트에서 깨진 selectors를 쫓고 있었습니다. 주말이 지날 무렵, 저는 try/except 블록과 regex 해킹이 뒤섞인 400줄짜리 Python 스크립트를 갖게 되었습니다. 사이트의 레이아웃이 바뀔 때마다, 제 스크래퍼는 필드의 절반에 대해 조용히 None을 반환했습니다.

마치 HTML을 가지고 두더지 잡기 게임을 하는 기분이었습니다.

시도했지만 실패했던 것들

첫째, 더 나은 selectors를 만드는 데 집중했습니다. 속도를 위해 lxml을 사용했고, "견고한" XPath 표현식을 작성했지만(실제로는 그렇지 않았습니다), 각 필드에 대한 fallback 패턴을 추가했습니다. 제품 페이지가 10개인 사이트에서는 작동했습니다. 하지만 50개라면? 불가능했습니다.

그다음으로 sitemap scraping을 시도했습니다. 사이트맵을 가져와서 파싱하고, 콘텐츠가 일관되기를 바라는 방식이었습니다. 하지만 이는 문제의 위치만 옮겼을 뿐입니다. 사이트맵은 종종 누락되어 있거나 깨져 있었습니다.

다음으로 visual regression을 시도했습니다. 스크린샷을 찍고 OCR로 가격을 읽어내는 방식입니다. 데모에서는 작동했지만, 동적인 레이아웃과 일관되지 않은 폰트 때문에 운영 환경(production)에서는 실패했습니다.

심지어 가격 요소를 식별하기 위해 machine learning classifiers를 시도하기도 했습니다 (CSS class name, 폰트 크기, 위치와 같은 특징(features)을 사용). 1,000개의 주석 처리된 요소로 random forest를 학습시켰습니다. 테스트 세트에서 85%의 정확도를 달성했지만, 나머지 15% 때문에 수백 개의 제품을 수동으로 수정해야 했습니다. 용납할 수 없는 수준이었습니다.

결국 성공한 방법 (접근 방식)

새벽 2시에 또 다른 AttributeError를 뚫어지게 쳐다보다가, 문득 이런 생각이 들었습니다. HTML 구조를 파싱하려는 시도를 완전히 멈추고, 대신 언어 모델(language model)이 인간처럼 원시 HTML을 읽게 하면 어떨까?

아이디어는 간단합니다. CSS 선택자 (CSS selectors)를 사용하는 대신, 컨테이너 요소(예: 상품 카드)의 내부 HTML (inner HTML)을 LLM 프롬프트에 쏟아붓고 필요한 필드를 추출해 달라고 요청하는 것입니다.

import openai

# 데모용 – 본인의 API 키로 교체하세요
...

물론, 매번 50KB의 HTML을 LLM에 던질 수는 없습니다 (비용 + 지연 시간 (latency) 문제). 핵심은 페이지를 사전 분할 (pre-segment) 하는 것입니다. 메인 콘텐츠 영역을 찾는 간단한 휴리스틱 (heuristic) (예: "product", "item", 또는 "card"를 포함하는 클래스를 가진 <div>)을 사용하여 해당 스니펫 (snippet)만 전달하세요.

저는 다음과 같은 작은 파이프라인 (pipeline)을 구축했습니다:

  1. requests를 사용하여 페이지를 가져옵니다.
  2. BeautifulSoup로 파싱합니다 – 하지만 [class*="product"] 또는 [id*="product"]와 같은 일반적인 선택자를 사용하여 후보 컨테이너를 찾는 용도로 사용합니다.
  3. 각 후보 컨테이너에 대해, 그 내부 HTML (약 3,000자까지)을 LLM에 전달합니다.
  4. LLM이 구조화된 JSON을 반환합니다. 결과들을 병합합니다.

실제로 작동하는 코드

다음은 한 페이지에 여러 상품이 있는 경우를 처리하는 더 현실적인 버전입니다:

import requests
from bs4 import BeautifulSoup
import openai
...

이 코드는 에러 처리 (error handling)와 속도 제한 (rate limiting)이 없으므로 바로 프로덕션 (production) 환경에 사용할 수는 없지만, 핵심 아이디어를 보여줍니다.

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

비용 (Cost): 각 추출에는 상품당 약 0.01¢ ~ $0.05가 소요됩니다 (모델 및 토큰 수에 따라 다름). 1,000개의 상품의 경우 $10~$50가 됩니다. 수동 데이터 입력보다는 저렴하지만, 공짜는 아닙니다.

지연 시간 (Latency): GPT-4는 호출당 2~5초가 걸립니다. 호출 횟수를 줄이기 위해 컨테이너들을 하나의 프롬프트로 배치 (batch) 처리할 수 있습니다 (예: "다음 5개의 HTML 스니펫에서 모든 상품을 추출하세요...").

신뢰성 (Reliability): LLM은 환각 (hallucinate) 현상을 일으킵니다. 존재하지 않는 가격을 지어내거나 잘못된 형식의 JSON을 반환하는 것을 본 적이 있습니다. 항상 출력값을 검증하세요 (예: price가 정규 표현식 (regex)과 일치하는지 확인).

토큰 제한 (Token limits): 각 스니펫은 약 3,000자 정도가 될 수 있습니다. 전체 상품 페이지의 DOM을 처리하려면 먼저 HTML을 청크 (chunk)로 나누거나 요약해야 합니다. html2text와 같은 도구가 도움이 될 수 있습니다.

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

  • HTML 구조가 안정적인 단일 사이트를 스크래핑하는 경우 → 전통적인 셀렉터 (selectors) 방식이 더 빠르고 저렴합니다.
  • 실시간(1초 미만) 스크래핑이 필요한 경우 → LLM은 2초 이상의 지연 시간 (latency)을 추가합니다.
  • 수천 개의 페이지를 처리해야 하는 경우 → 비용이 폭증할 수 있습니다.

다음에 제가 다르게 시도할 것들

첫째, 저는 **하이브리드 접근 방식 (hybrid approach)**으로 시작할 것입니다. 구조가 알려져 있고 안정적인 사이트에는 전통적인 셀렉터를 사용하고, 구조가 복잡한 소수의 사이트에 대해서만 LLM으로 전환하는 방식입니다. 이렇게 하면 비용과 지연 시간을 줄일 수 있습니다.

둘째, LLM 응답을 캐싱 (cache)하겠습니다. 동일한 HTML 스니펫이 다시 나타나는 경우(예: 서로 다른 방문 시 동일한 제품이 나타남), 추출된 JSON을 재사용하는 것입니다.

셋째, 추출을 위해 더 작고 특화된 모델 (smaller, specialised models) (예: phi-3 또는 Ollama를 통한 로컬 LLM)을 탐색하겠습니다. 이들은 속도는 느리지만 호출당 비용이 들지 않으며 오프라인에서 실행할 수 있습니다.

마지막으로, 추출된 데이터를 알려진 정답 (ground truth)과 비교하여 검증하는 **테스트 하네스 (test harness)**를 구축하는 데 시간을 투자하겠습니다. 이것 없이는 눈을 가리고 비행하는 것과 같습니다.

이 접근 방식 덕분에 매주 스크래퍼를 새로 작성해야 하는 상황을 피할 수 있었습니다. 이것이 만능 해결책 (silver bullet)은 아니지만, 취약한 셀렉터에서 유연한 언어 이해 (language understanding)로 전환하는 실용적인 변화입니다. #ai.interwestinfo.com/ 과 같은 도구들도 유사한 추출 API를 제공하지만, 핵심 아이디어는 동일합니다. 개발자가 아닌 모델이 구조를 파싱하게 만드는 것입니다.

여러분의 생각은 어떠신가요? 데이터 추출을 위해 LLM을 사용해 보셨나요, 아니면 여전히 전통적인 셀렉터 방식을 선호하시나요? 여러분의 사용 사례에는 어떤 방식이 효과적인지 궁금합니다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0