프록시 트래픽을 기가바이트 단위로 지불할 때, 페이지의 모든 바이트는 동일한 비용이 듭니다: 찾아온 단락, 그 주변의 메뉴, 분석 스크립트, 히어로 이미지, 폰트 파일까지 모두 마찬가지입니다. 대부분의 팀은 페이지가 무겁다는 것을 알고 있습니다. 하지만 다운로드한 것 중 실제로 보관하는 데이터가 얼마나 되는지 아는 사람은 거의 없습니다.
저희는 이를 측정했습니다. 2026년 10월 8일, Tranco 순위 상위 1,000개 도메인의 홈페이지를 가져와 각 페이지에서 기사 형식의 링크를 하나씩 따라간 뒤, 바이트가 어디로 가는지 측정했습니다: 전송 중, HTML 내부, 그리고 브라우저가 다운로드하는 모든 것에서 말이죠. 이 연구는 그 결과와 사용한 코드, 그리고 이것이 스크래핑 예산에 어떤 의미가 있는지를 공유합니다.
Key takeaways
- 일반적인 페이지의 본문 콘텐츠는 HTML의 약 1%에 불과합니다. 중앙값 콘텐츠 페이지에서 3.2 KB의 본문 텍스트가 260 KiB의 HTML 안에 들어 있었습니다.
- 압축이 비용 절감의 대부분을 무료로 해결합니다. 동일한 HTML이 전송 중에는 46 KiB로 도착했는데, 이는 약 5.4배 더 작은 크기이며, 따라서 본문 텍스트는 실제로 전송된 바이트의 약 6%였습니다.
- 인라인 스크립트가 HTML에서 가장 큰 단일 비중을 차지합니다. 전체 콘텐츠 페이지에서 인라인 JavaScript는 HTML 바이트의 38%, 인라인 CSS는 15%, 인라인 SVG는 10%를 차지했습니다. 본문 텍스트는 1.3%였습니다.
- 브라우저는 비용을 몇 배로 늘립니다. 중앙값 콘텐츠 페이지는 헤드리스 브라우저에서 2.6 MB와 81건의 요청을 끌어왔습니다. HTML 문서 자체는 그중 약 3%에 불과했습니다.
- 이미지, 미디어, 폰트를 차단하면 브라우저 트래픽이 절반으로 줄어듭니다. 전체 페이지에서 이는 바이트의 48%를 제거했으며, 중앙값 페이지에서 경량 로드는 전체 로드의 71%였습니다.
How we measured
- 샘플. Tranco 목록(목록 Q2K34)의 상위 1,000개 도메인입니다. 이 중 다수는 웹사이트가 없는 API, CDN, 추적 호스트이므로, 431개의 개별 사이트가 사용 가능한 홈페이지를 반환했습니다. 각 홈페이지에서 콘텐츠 페이지로 보이는 첫 번째 동일 사이트 내 링크(로그인, 법적 고지, 도움말 페이지를 제외하고 네 단어 이상의 슬러그로 끝나는 경로)를 따라갔으며, 이를 통해 278개의 사용 가능한 콘텐츠 페이지를 얻었습니다.
- HTML 레이어. 일반 데스크톱 브라우저 User-Agent를 사용하고 압축을 활성화한 상태로 페이지당 한 번씩 요청했습니다. 수신된 바이트를 기록하고 압축을 해제한 뒤, HTML을 인라인 스크립트, 인라인 스타일(
style속성 포함), 인라인 SVG, JSON-LD로 분리했습니다. 오픈소스 추출 라이브러리인 trafilatura를 사용해 본문 콘텐츠를 추출하고, 그 바이트 수를 정규화된 텍스트로 집계했습니다. - 브라우저 레이어. 각 콘텐츠 페이지를 헤드리스 Chromium에서 로드하고, load 이벤트 발생 후 3초를 추가로 기다린 뒤, 모든 응답에 대해 리소스 유형별로 전송된 바이트를 합산했습니다. 그런 다음 이미지, 미디어, 폰트를 차단한 상태로 다시 로드했습니다.
- 제외 대상. 오류 응답, HTML이 아닌 응답, 차단 페이지(챌린지 페이지, “접근 거부” 페이지, 거의 비어 있는 응답)는 분석 전에 제외했으며, 따라서 결과는 실제로 콘텐츠를 제공한 페이지를 설명합니다.
The HTML layer
| Measure (median per page) | Content pages | Homepages |
|---|---|---|
| 측정된 페이지 수 | 278 | 431 |
| 전송 바이트 | 46 KiB | 51 KiB |
| 압축 해제 후 HTML | 260 KiB | 270 KiB |
| 본문 텍스트 | 3.2 KB | 1.4 KB |
| HTML 대비 본문 비중 | 1.2% | 0.6% |
| 전송 바이트 대비 본문 비중 | 6.2% | 3.1% |
| HTML 대비 전체 표시 텍스트 비중 | 3.6% | 2.4% |
예상대로 콘텐츠 페이지는 홈페이지보다 더 많은 텍스트를 담고 있지만, 전체적인 양상은 동일합니다: 스크래퍼가 보관하는 텍스트는 다운로드한 문서의 아주 작은 조각에 불과합니다. 메뉴, 푸터, 쿠키 안내까지 포함해 페이지의 모든 표시 단어를 세더라도, 텍스트는 HTML의 4% 미만이었습니다.
나머지는 어디로 갈까요? 278개 콘텐츠 페이지 전체를 기준으로 보면:
| Part of the HTML | Share of HTML bytes |
|---|---|
| 인라인 JavaScript | 37.5% |
| 인라인 CSS 및 style 속성 | 15.4% |
| 인라인 SVG | 9.8% |
| JSON-LD 구조화 데이터 | 0.9% |
| 본문 텍스트 | 1.3% |
| 마크업, 속성, 기타 전체 | 35.1% |
인라인 JavaScript는 단일 블록으로는 가장 큰 비중을 차지합니다: 프레임워크 상태, 설정, 추적 코드가 페이지에 직접 삽입되어 있습니다. 최신 사이트는 흔히 전체 페이지 데이터를 script 태그 안에 JSON 덩어리로 전달하고 클라이언트에서 렌더링하는데, 이것이 스크립트가 결국 표시되는 텍스트보다 더 많은 비중을 차지하는 이유입니다.
이는 또한 기회이기도 합니다. 이 페이지들의 JSON-LD는 평균적으로 HTML의 1% 미만이었으며, 페이지가 데이터를 JSON으로 삽입하는 경우, 그 덩어리는 대개 렌더링된 마크업보다 파싱하기 훨씬 깔끔합니다. HTML을 파싱하는 대신 JSON-LD를 추출하는 방법과 페이지 뒤에 숨은 API 찾기에 관한 저희 가이드에서 두 가지 모두를 다룹니다.
이 레이어에서는 압축이 그 무엇보다 중요합니다. 중앙값 페이지는 전송 중 5.4배 줄어들었습니다. 278개 콘텐츠 페이지 중 압축 없이 제공된 것은 10개뿐이었지만, 압축을 요청하지 않는 스크래퍼는 매번 전체 260 KiB를 받게 됩니다. HTTP 클라이언트가 Accept-Encoding: gzip, deflate, br을 전송한다면, 이미 260이 아니라 46 KiB에 대한 비용만 지불하고 있는 것입니다.
The browser layer
브라우저에서 페이지를 렌더링하면 문서뿐만 아니라 페이지가 요청하는 모든 것을 다운로드하게 됩니다.
| Measure (per content page) | Value |
|---|---|
| 브라우저에서 측정된 페이지 수 | 274 |
| 전송 바이트 중앙값, 전체 로드 | 2.6 MB |
| 페이지의 중간 절반 | 1.2 to 4.3 MB |
| 요청 수 중앙값, 전체 로드 | 81 |
| 전체 로드 대비 HTML 문서 비중(중앙값) | 3% |
| 전체 로드 대비 본문 텍스트 비중(중앙값) | 0.13% |
리소스 유형별로, 전체 로드 기준:
| Resource type | Share of bytes |
|---|---|
| 이미지 | 38.1% |
| 스크립트 | 35.9% |
| 미디어(비디오 및 오디오) | 7.4% |
| 폰트 | 6.6% |
| 스타일시트 | 2.8% |
| HTML 문서 | 2.4% |
| API 호출(fetch 및 XHR) | 4.9% |
| 기타 | 1.9% |
스크래퍼가 거의 필요로 하지 않는 이미지, 미디어, 폰트를 차단하면 중앙값 페이지가 1.5 MB와 55건의 요청으로 줄어들었습니다. 전체 페이지를 기준으로, 차단된 로드는 전체 로드 바이트의 52%를 전송했습니다. 스크립트는 안전하게 차단하기가 더 어려운데, 페이지가 찾아온 콘텐츠를 렌더링하는 데 스크립트가 필요한 경우가 많기 때문입니다.
What it means for a scraping budget
한 달에 100만 개의 콘텐츠 페이지를 수집하고 본문 텍스트만 보관하는 작업을 생각해 봅시다. 위의 중앙값을 사용하면:
| How the pages are fetched | Traffic per million pages |
|---|---|
| 본문 텍스트만, 그것만 가져올 수 있다면 | about 3.2 GB |
| HTML만, 압축됨 | about 47 GB |
| HTML만, 압축 안 됨 | about 265 GB |
| 브라우저, 이미지/미디어/폰트 차단 | about 1.5 TB |
| 브라우저, 전체 로드 | about 2.6 TB |
첫 번째 행과 마지막 행의 차이는 약 800배입니다. 여기서 실질적인 절감 순서가 도출됩니다:
- 데이터가 HTML 안에 있을 때는 언제나 브라우저 없이 HTML을 가져오세요. 그것만으로도 기가바이트와 테라바이트의 차이가 생깁니다.
- 항상 압축을 요청하세요. 비용 없이 HTML 트래픽을 약 5배 줄여줍니다.
- 렌더링이 꼭 필요할 때는 이미지, 미디어, 폰트를 차단하세요. 브라우저 트래픽을 대략 절반으로 줄여줍니다.
- 같은 데이터의 더 작은 출처를 찾으세요: JSON-LD, 삽입된 JSON 덩어리, 또는 페이지 자체가 호출하는 API 등입니다.
프록시 대역폭 비용 절감하기 가이드에서 이러한 기법들을 실제로 적용하는 방법을 다루며, 월간 대역폭 추정하기에서는 페이지 무게를 계획으로 전환하는 방법을 보여줍니다.
Measure your own pages
상위 사이트의 중앙값은 출발점일 뿐이며, 중요한 것은 여러분의 대상 사이트입니다. 아래 함수는 이 연구와 동일한 방법을 사용해 페이지 하나를 가져와 바이트가 어디로 가는지 보고합니다:
import gzip
import re
import zlib
import brotli
import requests
import trafilatura
from lxml import html as lxml_html
def decode(raw, content_encoding):
"""Undo Content-Encoding by hand, so we can count the compressed bytes first."""
for coding in reversed([c.strip() for c in content_encoding.lower().split(",") if c.strip()]):
if coding == "gzip":
raw = gzip.decompress(raw)
elif coding == "br":
raw = brotli.decompress(raw)
elif coding == "deflate":
raw = zlib.decompress(raw)
return raw
def size(text):
return len(text.encode("utf-8"))
def payload_breakdown(url, session=None):
"""Where the bytes of one HTML page go, from the wire down to the main content."""
session = session or requests.Session()
response = session.get(url, timeout=30, stream=True,
headers={"Accept-Encoding": "gzip, deflate, br"})
wire = response.raw.read(decode_content=False)
page = decode(wire, response.headers.get("Content-Encoding", "")).decode(
response.encoding or "utf-8", errors="replace")
doc = lxml_html.document_fromstring(page)
scripts = doc.xpath("//script")
json_ld = sum(size(s.text or "") for s in scripts if s.get("type") == "application/ld+json")
inline_js = sum(size(s.text or "") for s in scripts
if not s.get("src") and s.get("type") != "application/ld+json")
inline_css = sum(size(s.text or "") for s in doc.xpath("//style")) + sum(size(v) for v in doc.xpath("//@style"))
inline_svg = sum(size(lxml_html.tostring(s, encoding="unicode"))
for s in doc.xpath("//*[local-name()='svg'][not(ancestor::*[local-name()='svg'])]"))
main_text = trafilatura.extract(page, include_tables=True) or ""
return {
"status": response.status_code,
"wire_bytes": len(wire),
"html_bytes": size(page),
"inline_js": inline_js,
"inline_css": inline_css,
"inline_svg": inline_svg,
"json_ld": json_ld,
"main_text": size(re.sub(r"\s+", " ", main_text).strip()),
}
이 함수는 압축 해제 전에 응답 본문을 읽으므로 wire_bytes는 실제로 전송된 양입니다. 그런 다음 수동으로 디코딩합니다. 여러분의 대상 사이트 각각에서 몇 개의 페이지에 대해 실행해 보세요:
from payload import payload_breakdown
import requests
session = requests.Session()
session.headers["User-Agent"] = "ExampleStudy/1.0 (+https://example.com/bot)"
b = payload_breakdown("https://en.wikipedia.org/wiki/Web_scraping", session)
print(b)
print(f"main content: {b['main_text'] / b['html_bytes']:.1%} of the HTML, "
f"{b['main_text'] / b['wire_bytes']:.1%} of the bytes transferred")
{'status': 200, 'wire_bytes': 46860, 'html_bytes': 236286, 'inline_js': 6964, 'inline_css': 6303, 'inline_svg': 0, 'json_ld': 640, 'main_text': 26979}
main content: 11.4% of the HTML, 57.6% of the bytes transferred
Wikipedia는 이 기준으로 보면 효율적인 페이지입니다: 본문 텍스트가 HTML의 11% 이상을 차지하며, 이는 저희가 측정한 중앙값의 거의 10배입니다. 여러분의 대상 사이트도 이 범위 어딘가에 위치할 것이며, 어디에 위치하는지 알면 HTML만 수집하는 방식, 압축, 또는 다른 데이터 출처 중 무엇이 가장 큰 절감 효과를 가져올지 알 수 있습니다.
Limits of the measurement
- 상위 사이트에 한정됨. 상위 1,000개 도메인은 규모가 크고 잘 설계된 사이트입니다. 더 작은 사이트는 더 가볍거나 훨씬 더 무거울 수 있습니다.
- 사이트당 콘텐츠 페이지 1개. 각 홈페이지에서 첫 번째 기사 형식 링크를 따라갔습니다. 상품 페이지, 검색 결과, 목록 페이지는 다를 수 있습니다.
- 본문 콘텐츠는 추정치임. 추출 라이브러리는 특히 대부분 내비게이션으로 이루어진 페이지에서 콘텐츠를 누락하거나 과도하게 포함할 수 있습니다. 본문 텍스트 수치는 근사치로 간주하시고, 바이트 측정값은 정확합니다.
- 단일 스냅샷, 단일 네트워크. 페이지는 2026년 10월 8일, 유럽의 단일 네트워크에서 한 번 가져왔습니다. 사이트는 위치와 시간에 따라 다른 페이지, 광고, 미디어를 제공합니다.
- 브라우저 로드는 상한이 있었음. load 이벤트 후 3초가 지나면 측정을 중단했습니다. 그 이후에도 콘텐츠를 계속 로드하는 페이지는 저희가 기록한 것보다 더 많이 전송했을 것입니다.
FAQ
웹 페이지에서 실제 콘텐츠는 어느 정도의 비중을 차지하나요?
상위 1,000개 사이트 중 중앙값 콘텐츠 페이지에서, 본문 텍스트는 HTML의 약 1.2%, 전송된 압축 바이트의 약 6%였습니다. 전체 브라우저 로드에서는 바이트의 약 0.13%였습니다.
이미지를 차단하면 프록시 대역폭이 줄어드나요?
네. 헤드리스 브라우저에서 이미지, 미디어, 폰트를 차단하면 저희 샘플 전체에서 바이트의 48%가 제거되었습니다. 이미지만으로도 브라우저 트래픽의 38%를 차지했습니다.
브라우저 없이 스크래핑하는 것이 더 저렴한가요?
대개 큰 차이로 그렇습니다. 중앙값 콘텐츠 페이지는 압축된 HTML로는 46 KiB였지만 전체 브라우저 로드에서는 2.6 MB로, 50배 이상의 차이가 났습니다.
스크래핑 시 압축된 응답을 요청해야 하나요?
네. 중앙값 페이지는 압축 시 5.4배 더 작았습니다. 대부분의 HTTP 클라이언트는 기본적으로 압축을 요청하지만, 그렇지 않은 클라이언트는 모든 페이지의 전체 크기만큼 비용을 지불하게 되므로 반드시 확인하세요.
The bottom line
스크래퍼가 보관하는 데이터는 다운로드하는 것의 극히 일부에 불과합니다: 일반적인 페이지 HTML의 약 1%, 브라우저가 끌어오는 양의 1%도 안 되는 일부입니다. 이러한 오버헤드의 대부분은 피할 수 있습니다. 가능하면 렌더링 대신 HTML을 가져오고, 압축을 항상 켜 두고, 렌더링이 꼭 필요할 때는 무거운 리소스를 차단하고, 훨씬 적은 바이트로 동일한 정보를 담은 구조화 데이터나 API를 찾아보세요.
Sources and references
- Tranco, 목록 Q2K34, 샘플에 사용된 연구 지향적 상위 사이트 순위.
- Barbaresi, A. (2021). Trafilatura: A Web Scraping Library and Command-Line Tool for Text Discovery and Extraction. ACL 2021 System Demonstrations.
- 위 코드와 헤드리스 Chromium을 사용해 Shifter가 2026년 10월 8일에 측정한 페이지.