← 블로그로 돌아가기

숨겨진 API로 HTML 파싱 대신하기: 트래픽과 프록시 비용을 10배 줄이는 방법

HTML 페이지 파싱이 프록시 예산을 소모하는 이유를 분석하고, 트래픽을 10배 줄이는 실제 방법으로 숨겨진 API로 전환하는 방법을 보여줍니다.

📅2026년 9월 27일

Wildberries, Ozon 또는 다른 웹사이트를 전체 HTML 페이지를 통해 파싱하는 경우, 프록시 트래픽에 대해 5-10배 더 많은 비용을 지불하고 있습니다. 각 제품 카드 페이지는 200-800 KB의 마크업, 스크립트 및 스타일로 구성되어 있으며, 실제로 필요한 필드는 가격, 재고, 평점 등 몇 가지에 불과합니다. 이 기사에서는 웹사이트의 숨겨진 API를 찾고 동일한 데이터를 직접 컴팩트한 JSON 형식으로 받는 방법을 설명합니다.

왜 HTML 파싱이 프록시 트래픽을 소모하는가

파서가 일반 HTTP 요청이나 헤드리스 브라우저(Selenium, Puppeteer, Playwright)를 통해 페이지를 로드할 때, 서버는 전체 HTML 문서: 마크업, 인라인 스크립트, 스타일, 때때로 base64 이미지 및 광고 위젯에 대한 데이터가 포함된 수백 줄의 JSON을 반환합니다. 평균적으로 Wildberries의 제품 카드는 300-600 KB, Ozon에서는 모든 관련 리소스(CSS, 폰트, 트래커)를 포함할 경우 최대 800 KB입니다.

만약 하루에 10,000개의 제품을 3개의 프록시 세션을 통해 모니터링한다면, 이는 쉽게 월 수십 기가바이트의 트래픽으로 이어질 수 있습니다. 레지던트 및 모바일 프록시는 일반적으로 트래픽 기준으로 판매되므로, 여분의 메가바이트는 직접적인 비용으로 이어집니다. 실제로 필요한 데이터인 가격, 할인, 재고, 평점은 JSON 응답에서 1-5 KB를 차지합니다. 하나의 제품에 대한 차이는 100-200배이며, 브라우저 렌더링에 대한 간접 비용을 고려하면 시간과 CPU 절약은 더욱 커집니다.

HTML 파싱의 추가적인 문제는 취약성입니다. 마켓플레이스 웹사이트는 정기적으로 레이아웃, CSS 클래스, DOM 구조를 변경합니다. 이러한 변화는 XPath 또는 CSS 선택기를 기반으로 구축된 파서를 망가뜨립니다. 내부 API는 모바일 애플리케이션과 웹사이트의 프론트엔드가 동시에 작동하기 때문에 훨씬 덜 자주 변경됩니다.

숨겨진 API란 무엇이며 어디서 오는가

거의 모든 현대 웹사이트는 SPA(Single Page Application) 또는 하이브리드 애플리케이션으로, 브라우저가 먼저 페이지의 "뼈대"를 로드한 후 JavaScript를 통해 내부 API에 대한 추가 요청을 하여 실제 데이터(가격, 재고, 리뷰, 추천)를 가져옵니다. 이러한 요청은 숨겨진 또는 내부 API라고 하며, 공개적으로 문서화되어 있지 않지만 브라우저의 트래픽에서 완전히 열려 있습니다.

기술적으로 이는 일반적으로 JSON 형식으로 데이터를 반환하는 REST 또는 GraphQL 엔드포인트입니다. 예를 들어, Wildberries의 제품 카드는 card.wb.ru 및 wbx-content-v2.wbstatic.net와 같은 요청을 통해 로드되며, 가격 및 재고는 basket-01.wb.ru 및 유사한 도메인에 대한 별도의 요청으로 전달됩니다. Ozon에서도 유사한 논리가 적용됩니다: 프론트엔드는 내부 composer API에 접근하여 마이크로서비스에서 데이터를 집계합니다.

중요한 점은 이러한 API를 사용하는 것이 공식적으로 해킹이 아니라는 것입니다. 사용자는 단순히 일반 브라우저가 수행하는 것과 동일한 요청을 반복하는 것입니다. 그러나 웹사이트는 이러한 엔드포인트를 안티봇 시스템으로 보호하므로, 이후에는 실제 클라이언트의 행동을 신중하게 모방해야 하며, 고품질 프록시를 사용하는 것이 포함됩니다.

DevTools를 통해 숨겨진 API 찾기

내부 API를 찾는 것은 코드 한 줄 없이 Chrome 또는 Firefox의 내장 도구를 사용하여 가능합니다. 단계별 알고리즘은 다음과 같습니다:

  1. Chrome에서 필요한 제품 페이지를 열고 F12를 눌러 Network 탭으로 이동합니다.
  2. 요청 필터에서 Fetch/XHR 유형을 선택하여 이미지, 폰트 및 정적 파일의 로드를 차단합니다.
  3. 페이지를 새로 고침(F5)하고 페이지 뼈대 로드 후 나타난 요청 목록을 확인합니다.
  4. 응답(응답 탭)에서 제품 가격, 이름 또는 JSON 형식의 다른 필요한 필드가 보이는 요청을 찾습니다.
  5. 해당 요청을 클릭하고 cURL로 복사합니다(마우스 오른쪽 버튼 → 복사 → cURL로 복사) — 이렇게 하면 전체 헤더, 쿠키 및 매개변수 세트를 얻을 수 있습니다.
  6. URL에서 어떤 매개변수가 필수인지(제품 아티클, 지역, API 버전) 확인하고, 어떤 것은 데이터 손실 없이 제거할 수 있는지 확인합니다.

이후에는 전체 페이지를 렌더링하는 대신 일반 HTTP 라이브러리를 통해 이 요청을 반복하기만 하면 됩니다. 이는 대부분의 마켓플레이스(Wildberries, Ozon, Avito) 및 Amazon, eBay와 같은 많은 해외 플랫폼에서도 작동합니다.

트래픽 비교: HTML vs JSON API

데이터 양의 차이가 너무 커서 숫자로 보여줄 필요가 있습니다. 아래는 인기 마켓플레이스에서 하나의 제품 카드에 대한 평균 측정값입니다.

파싱 방법 평균 응답 크기 로드 시간 JS 렌더링 필요
Selenium을 통한 전체 HTML 400-800 KB 1.5-4 초 예
간단한 HTTP 요청 (requests) 150-300 KB 0.3-0.8 초 아니오
숨겨진 JSON API 3-15 KB 0.1-0.3 초 아니오

하루에 50,000개의 제품을 모니터링할 경우, 헤드리스 브라우저에서 API에 대한 직접 요청으로 전환하면 트래픽이 약 30-40 GB에서 300-700 MB로 줄어듭니다. 이는 프록시 트래픽 절약뿐만 아니라 파서의 서버 인프라에 대한 부하를 줄이는 데도 도움이 됩니다 — 렌더링에 필요한 CPU가 줄어들고, 메모리가 적게 소모되며, 데이터 수집 속도가 빨라집니다.

Python을 이용한 실용적인 예제

간단한 예제를 살펴보겠습니다: 전체 페이지를 로드하는 대신 내부 API에 대한 직접 요청을 통해 제품의 가격과 재고를 가져오는 방법입니다. 이는 학습 템플릿이며, 특정 웹사이트에 대한 정확한 엔드포인트와 매개변수는 DevTools를 통해 결정해야 합니다. 요청 구조는 지역 및 API 버전에 따라 다를 수 있습니다.

import requests

def get_product_data(product_id: str, proxies: dict = None) -> dict:
    """
    전체 HTML 대신 내부 API를 통해 제품 데이터를 가져옵니다.
    proxies는 requests 형식의 프록시 사전입니다: {"http": "...", "https": "..."}
    """
    url = f"https://card.example-marketplace.ru/v2/detail"
    params = {
        "nm": product_id,
        "dest": "-1257786",  # 지역, DevTools를 통해 결정
        "spp": "0"
    }
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                       "AppleWebKit/537.36 (KHTML, like Gecko) "
                       "Chrome/120.0 Safari/537.36",
        "Accept": "application/json",
        "Referer": f"https://www.example-marketplace.ru/catalog/{product_id}/detail.aspx"
    }

    response = requests.get(
        url,
        params=params,
        headers=headers,
        proxies=proxies,
        timeout=10
    )
    response.raise_for_status()
    data = response.json()

    product = data["products"][0]
    return {
        "id": product["id"],
        "name": product["name"],
        "price": product["salePriceU"] / 100,
        "stock": product.get("totalQuantity", 0),
        "rating": product.get("reviewRating", None)
    }


if __name__ == "__main__":
    proxy = {
        "http": "http://user:pass@proxy-host:port",
        "https": "http://user:pass@proxy-host:port"
    }
    result = get_product_data("123456789", proxies=proxy)
    print(result)

이 예제에서 세 가지 사항에 주목하십시오. 첫째, 많은 API가 요청이 "브라우저에서" 온 것인지 확인하기 때문에 Referer 헤더를 지정합니다. 둘째, 우리는 라이브러리 requests의 기본 User-Agent 대신 현실적인 User-Agent를 사용합니다. 셋째, 전체 요청이 렌더링 없이 하나의 HTTP 호출로 이루어지며, 이는 트래픽과 속도에서 큰 이점을 제공합니다.

GraphQL 엔드포인트의 경우 논리는 유사하지만 GET 매개변수 대신 JSON 형식의 요청 본문으로 POST 요청을 보내며, 필요한 필드를 명시적으로 나열합니다. 이는 서버가 요청된 데이터만 반환하므로 응답 크기를 더욱 줄입니다.

API 요청 시 프록시 사용하기

컴팩트한 JSON 형식으로 전환하더라도 여전히 프록시가 필요합니다. 마켓플레이스는 하나의 IP에서 요청 수를 제한하고 비정상적인 활동 시 차단합니다. 이 경우 프록시 유형의 올바른 선택이 파서의 안정성에 직접적인 영향을 미칩니다.

Wildberries 또는 Ozon과 같은 마켓플레이스의 API를 대량으로 우회하는 데는 데이터 센터 프록시가 적합합니다. 이들은 높은 속도와 낮은 트래픽 비용을 제공하여 경량 JSON 엔드포인트에 대한 빈번한 요청 시 매우 중요합니다. 그러나 특정 API가 더 강력한 안티봇으로 보호되고 데이터 센터 서브넷을 전체적으로 차단하는 경우, 레지던트 프록시로 전환하는 것이 더 합리적입니다. 이들은 실제 가정 사용자 IP 주소를 사용하여 서브넷 차단에 덜 걸립니다.

모바일 애플리케이션에 의존하는 API(일부 버전의 Avito 엔드포인트 또는 마켓플레이스는 모바일 트래픽에서만 데이터를 반환)에는 모바일 프록시를 통해 연결해야 할 수 있습니다. 이들은 실제 이동통신 사업자의 트래픽을 모방하여 일반 IP를 차단하는 검사를 통과합니다.

파서에서 프록시를 설정할 때는 요청을 시간적으로 분산시키고 IP를 회전시키는 것도 중요합니다. 심지어 컴팩트한 JSON 요청도 1,000번을 1분에 동일한 주소에서 반복하면 보안 시스템의 의심을 초래할 수 있습니다. 여러 프록시 세션의 풀을 설정하고 그 사이에 1-3초의 임의 지연을 추가하여 부하를 분산시키십시오.

함정: 토큰, 서명, 안티봇

숨겨진 API는 항상 완전히 열려 있는 것은 아닙니다. 일부 웹사이트는 파서를 구축할 때 고려해야 할 추가 메커니즘으로 엔드포인트를 보호합니다.

  • 세션의 임시 토큰 — 일부 API는 토큰을 요청해야 하며, 이 토큰은 다음 요청의 헤더에 전달되고 제한된 시간(보통 5-30분) 동안 유효합니다.
  • 요청 서명 (signature) — 요청 매개변수는 페이지의 JS 코드에서 비밀 키로 해시됩니다. 이러한 서명은 알고리즘을 분석하여 수동으로 재현하거나, 토큰을 얻는 단계에서만 헤드리스 브라우저를 사용하고 이후에는 직접 가벼운 요청을 보내야 합니다.
  • IP 및 User-Agent에 대한 속도 제한 — 요청 빈도가 초과되면 웹사이트가 일시적으로 접근을 차단합니다. 이는 프록시 회전 및 합리적인 지연으로 해결할 수 있습니다.
  • 헤더의 지문 인식 — 일부 시스템은 전체 헤더 세트를 검사하여(순서, Accept-Language, Sec-Fetch-*의 존재) "불완전한" 세트로 요청을 차단합니다. 이는 스크립트가 아닌 브라우저에 특유한 것입니다.
  • 데이터의 지역 의존성 — 마켓플레이스의 가격 및 재고는 지역에 따라 다를 수 있으므로, 요청에 올바른 지역/창고 매개변수를 전달하는 것이 중요합니다. 그렇지 않으면 데이터가 관련성이 없어집니다.

요청 서명으로 API가 차단된 경우, 타협적인 옵션은 헤드리스 브라우저(Playwright, Puppeteer)를 사용하여 네트워크 요청을 가로채고 준비된 JSON 응답을 추출하는 것입니다. 이는 DOM 파싱 없이 진행되므로 직접 HTTP 요청보다 느리지만, 여전히 페이지 레이아웃의 전체 파싱보다 빠르고 간편합니다.

파서 실행 전 체크리스트

  • DevTools를 통해 엔드포인트를 찾고, cURL로 복사하여 Postman 또는 requests에서 테스트했습니다.
  • 필수 요청 매개변수(제품 ID, 지역, API 버전)를 정의하고 불필요한 매개변수를 제외했습니다.
  • User-Agent, Referer 및 Accept-Language 헤더를 현실적으로 설정했습니다.
  • 세션 토큰 또는 요청 서명이 필요한지 확인하고, 이를 얻는 방법을 구상했습니다.
  • 프록시 회전 및 요청 간의 임의 지연을 설정했습니다.
  • 특정 웹사이트의 보호에 적합한 프록시 유형(데이터 센터, 레지던트 또는 모바일)을 선택했습니다.
  • 자동으로 다른 프록시로 전환하는 429 및 403 오류 처리 기능을 추가했습니다.
  • 실제 절약을 모니터링하기 위해 트래픽 양을 로깅하도록 설정했습니다.

결론

전체 HTML 파싱에서 숨겨진 API로의 전환은 단순한 기술 최적화가 아니라 프록시 트래픽 및 인프라 비용을 직접적으로 줄이는 것입니다. 수백 킬로바이트의 불필요한 마크업을 로드하는 대신, 가격, 재고 또는 평점을 모니터링하는 데 필요한 정확한 필드만 포함된 컴팩트한 JSON을 받게 됩니다. 추가적인 보너스는 웹사이트 레이아웃 변경에 대한 파서의 내구성이며, 내부 API는 프론트엔드보다 덜 자주 변경됩니다.

또한 API를 찾는 방법은 고품질 프록시의 필요성을 없애지 않습니다. 마켓플레이스의 안티봇 시스템은 HTML 요청과 JSON 엔드포인트 요청 모두를 동일하게 주의 깊게 모니터링합니다. Wildberries 또는 Ozon을 대량으로 모니터링하는 경우, 비용 절감을 위해 빠른 데이터 센터 프록시로 시작하고, 차단의 첫 징후가 보이면 레지던트 또는 모바일 IP 풀로 전환하여 파서의 안정성을 높이십시오.