cdpfleet Docs GitHub Dashboard

Cases / #30 · 2026-10-09 · Easy

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-09: ✓ 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, httpbin.org/html and scrapethissite.com: 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. Print both timings, the speedup 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) {
  const page = await browser.newPage();
  try {
    await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
    return { url, title: await page.title() };
  } finally {
    await page.close();
  }
}

try {
  const t1 = Date.now();
  const 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();
  const parallel = await Promise.all(URLS.map((url) => extract(browser, url).then((r) => ({ ...r, method: 'parallel' }))));
  const parSeconds = Math.round((Date.now() - t2) / 10) / 100;

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

What we got

MethodPagesSeconds
Sequential528.86
Parallel54.26
Speedup6.8x

From the Node.js run on 2026-10-09. 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": 28.86,
  "parallel_seconds": 4.26,
  "speedup": "6.8x",
  "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