cdpfleet Docs Sign up free

Examples & benchmarks / #30 · 2026-10-09 · Example · Easy · for web scraping

Five pages at once: parallel tabs in one browser session

Open five unrelated pages concurrently in one session and compare with loading them one by one — same thread, same proxy, a fraction of the wall time.

Chromium

Run on production on 2026-10-21: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

The problem

You need something from several unrelated pages. The obvious loop opens one, extracts, closes, and moves on — and most of each wait is latency through the proxy while the browser sits idle. A session can hold many tabs, and each language has its own way to wait on several at once. How much does that save, and what does it cost?

What we used, and why

WhatWhy
chromium, headless: "new"One session (1 thread) for all five pages.
Five independent URLsbooks.toscrape.com, quotes.toscrape.com, example.com, a Wikipedia article and the Hacker News front page: public, no login, safe to load at once.
browser.newPage() per URLEach tab has its own navigation; all share the session's proxy and connection.
Promise.all · asyncio.gather · Task.WhenAll · goroutinesEach language's own way to wait on five loads at once. Java starts all five navigations and then waits for each — no threads.
waitUntil: "domcontentloaded"Wait for the HTML, not every image and script: one slow asset would otherwise set the pace.
page.title()The smallest proof a page loaded; put your extraction here.

How it works

  1. Launch one Chromium session.
  2. Sequential: open each URL in a new tab, read its title, close it — timed.
  3. Parallel: open all five at once, read the titles, close the tabs — timed.
  4. Do both three times, alternating, and keep each method's best time: a single page that stalls in the proxy (we saw 14–28 s now and then) would otherwise decide one comparison on its own.
  5. Print both timings, the speedup, every round and every title.

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 URLS = [
  'https://books.toscrape.com/',
  'https://quotes.toscrape.com/',
  'https://example.com',
  'https://en.wikipedia.org/wiki/Web_scraping',
  'https://news.ycombinator.com/',
];

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: 'new' }),
});
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 } });

async function extract(browser, url) {
  // One retry, in a fresh tab: over 30 loads through a rotating proxy, one occasionally fails.
  for (let attempt = 1; ; attempt++) {
    const page = await browser.newPage();
    try {
      await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
      return { url, title: await page.title() };
    } catch (err) {
      if (attempt === 2) throw err;
    } finally {
      await page.close();
    }
  }
}

try {
  // Three rounds of each, alternating; the best time of each method counts. One page that
  // stalls for 20 s in the proxy would otherwise decide a single comparison on its own.
  const rounds = [];
  let sequential = [];
  let parallel = [];
  for (let round = 0; round < 3; round++) {
    const t1 = Date.now();
    sequential = [];
    for (const url of URLS) {
      sequential.push({ ...await extract(browser, url), method: 'sequential' });
    }
    const seqSeconds = Math.round((Date.now() - t1) / 10) / 100;

    const t2 = Date.now();
    parallel = await Promise.all(URLS.map((url) => extract(browser, url).then((r) => ({ ...r, method: 'parallel' }))));
    const parSeconds = Math.round((Date.now() - t2) / 10) / 100;
    rounds.push({ sequential_seconds: seqSeconds, parallel_seconds: parSeconds });
  }
  const seqSeconds = Math.min(...rounds.map((r) => r.sequential_seconds));
  const parSeconds = Math.min(...rounds.map((r) => r.parallel_seconds));

  console.log(JSON.stringify({
    sequential_seconds: seqSeconds,
    parallel_seconds: parSeconds,
    speedup: `${Math.round(seqSeconds / parSeconds * 10) / 10}x`,
    rounds,
    results: [...sequential, ...parallel],
  }, null, 2));
} finally {
  await browser.close();
}

What we got

MethodPagesSeconds
Sequential512.77
Parallel59.39
Speedup1.4x

From the Node.js run on 2026-10-21. 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)
{
  "sequential_seconds": 12.77,
  "parallel_seconds": 9.39,
  "speedup": "1.4x",
  "rounds": [
    {
      "sequential_seconds": 16.61,
      "parallel_seconds": 30.7
    },
    {
      "sequential_seconds": 15.14,
      "parallel_seconds": 9.39
    },
    {
      "sequential_seconds": 12.77,
      "parallel_seconds": 13.3
    }
  ],
  "results": [
    {
      "url": "https://books.toscrape.com/",
      "title": "All products | Books to Scrape - Sandbox",
      "method": "sequential"
    },
    {
      "url": "https://quotes.toscrape.com/",
      "title": "Quotes to Scrape",
      "method": "sequential"
    },
    {
      "url": "https://example.com",
      "title": "Example Domain",
      "method": "sequential"
    },
    {
      "url": "https://en.wikipedia.org/wiki/Web_scraping",
      "title": "Web scraping - Wikipedia",
      "method": "sequential"
    },
    {
      "url": "https://news.ycombinator.com/",
      "title": "Hacker News",
      "method": "sequential"
    },
    {
      "url": "https://books.toscrape.com/",
      "title": "All products | Books to Scrape - Sandbox",
      "method": "parallel"
    },
    {
      "url": "https://quotes.toscrape.com/",
      "title": "Quotes to Scrape",
      "method": "parallel"
    },
    {
      "url": "https://example.com",
      "title": "Example Domain",
      "method": "parallel"
    },
    {
      "url": "https://en.wikipedia.org/wiki/Web_scraping",
      "title": "Web scraping - Wikipedia",
      "method": "parallel"
    },
    {
      "url": "https://news.ycombinator.com/",
      "title": "Hacker News",
      "method": "parallel"
    }
  ]
}

Takeaways