cdpfleet Docs Sign up free

Examples & benchmarks / #8 · 2026-09-30 · Benchmark · Medium · for web scraping

A bandwidth diet for residential proxies: what blocking really saves

Count every byte a news homepage pulls through your proxy, then block images, media, fonts and third parties — and see where the money actually goes.

Chromium

Run on production on 2026-09-30: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

A benchmark: the results below are what this setup measured on the dates shown, on our own test runs. Sites, networks and browsers change, so rerun the code before you rely on the numbers.

The problem

Residential proxies bill per gigabyte, so a scraper's cost is mostly bytes. The usual advice is "block images". How much does that save on a real, ad-heavy page — and what else is worth blocking?

What we used, and why

WhatWhy
context.route('**/*') + route.abort()Drops requests before they leave the browser, so they never cost proxy bandwidth.
request.resourceType()Classifies requests: image, media, font, script, xhr, …
A first-party listThe site's own domains and CDNs (bbc.com, bbc.co.uk, bbci.co.uk); everything else is third-party (ads, analytics, trackers).
request.sizes() on requestfinishedExact bytes on the wire per request: request and response headers plus the encoded body.
A fresh context per variantEmpty cache each time, so every variant pays full price.

How it works

  1. Load the page with nothing blocked; count requests and bytes for DOM-ready plus 5 seconds.
  2. Again, blocking images, media and fonts.
  3. Again, also blocking every third-party host.
  4. Convert to proxy cost per 100,000 page loads at $3/GB.

The code

The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:

// npm install [email protected]
// env: CDPFLEET_API_KEY, PROXY_URL
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;
const PAGE = 'https://www.bbc.com/news';
const FIRST_PARTY = ['bbc.com', 'bbc.co.uk', 'bbci.co.uk']; // the site's own domains and CDNs
const PRICE_PER_GB = 3; // a typical residential proxy price, USD

const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({ proxy: process.env.PROXY_URL, headless: true }),
});
if (!res.ok) throw new Error(`launch: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });

const HEAVY = new Set(['image', 'media', 'font']);

// Load the page in a fresh context (empty cache) and count every byte on the wire.
async function measure(label, block) {
  const context = await browser.newContext();
  if (block) {
    await context.route('**/*', (route) => {
      const r = route.request();
      if (HEAVY.has(r.resourceType()) || (block === 'first-party' && !FIRST_PARTY.some((d) => new URL(r.url()).hostname.endsWith(d)))) {
        return route.abort();
      }
      return route.continue();
    });
  }
  const page = await context.newPage();
  let bytes = 0;
  let requests = 0;
  let blocked = 0;
  const count = async (req) => {
    const s = await req.sizes();
    bytes += s.requestHeadersSize + s.requestBodySize + s.responseHeadersSize + s.responseBodySize;
    requests++;
  };
  page.on('requestfinished', count);
  page.on('requestfailed', () => { blocked++; });
  const t = Date.now();
  // DOM ready, then a fixed 5 s for the rest to arrive: the same window for every variant
  // ('load' can wait forever on a blocked video).
  await page.goto(PAGE, { waitUntil: 'domcontentloaded', timeout: 90000 });
  const readyMs = Date.now() - t;
  await page.waitForTimeout(5000);
  const title = await page.title();
  page.off('requestfinished', count); // stop counting before closing
  await context.close();
  return { variant: label, title, requests, blocked, kilobytes: Math.round(bytes / 1024), dom_ready_ms: readyMs };
}

try {
  const rows = [
    await measure('everything', null),
    await measure('no images, media or fonts', 'heavy'),
    await measure('…and first-party only', 'first-party'),
  ];
  const full = rows[0].kilobytes;
  for (const r of rows) {
    r.saved = `${Math.round((1 - r.kilobytes / full) * 100)}%`;
    r.proxy_cost_per_100k_pages = `$${((r.kilobytes * 100000) / 1024 / 1024 * PRICE_PER_GB).toFixed(0)}`;
  }
  console.log(JSON.stringify(rows, null, 2));
} finally {
  await browser.close();
}

What we got

VariantRequestsBlockedKBSavedProxy cost / 100k pagesDOM ready (ms)
everything80016620%$4764314
no images, media or fonts842815338%$4395030
…and first-party only533179152%$2262688

From the Node.js run on 2026-09-30. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.

Raw output (Node.js)
[
  {
    "variant": "everything",
    "title": "BBC News - Breaking news, video and the latest top stories from the U.S. and around the world",
    "requests": 80,
    "blocked": 0,
    "kilobytes": 1662,
    "dom_ready_ms": 4314,
    "saved": "0%",
    "proxy_cost_per_100k_pages": "$476"
  },
  {
    "variant": "no images, media or fonts",
    "title": "BBC News - Breaking news, video and the latest top stories from the U.S. and around the world",
    "requests": 84,
    "blocked": 28,
    "kilobytes": 1533,
    "dom_ready_ms": 5030,
    "saved": "8%",
    "proxy_cost_per_100k_pages": "$439"
  },
  {
    "variant": "…and first-party only",
    "title": "BBC News - Breaking news, video and the latest top stories from the U.S. and around the world",
    "requests": 53,
    "blocked": 31,
    "kilobytes": 791,
    "dom_ready_ms": 2688,
    "saved": "52%",
    "proxy_cost_per_100k_pages": "$226"
  }
]

Takeaways