OmniScrapeOmniScrape
ProductsSolutionsGuidesDocs ↗PricingAbout
← All guides
Scraping Tools

Remote Browsers over CDP with Playwright and Puppeteer

A local browser fleet starts as one Chromium instance on a laptop and ends as a service somebody has to operate: process supervision, memory recycling, headroom for spikes, and an image that has to be rebuilt whenever Chrome ships. None of that is the thing you were trying to build.

Connecting over the Chrome DevTools Protocol moves the browser somewhere else while leaving your script alone. Playwright's connectOverCDP and Puppeteer's connect take a WebSocket endpoint instead of launching locally, and everything after that line is unchanged. This guide covers when that trade is worth making, how time-based billing changes the shape of your code, and where a per-request API is the cheaper answer.

On this page

1. One line changes, the rest of the script does not2. When a remote browser is the right tool3. When a per-request API is cheaper4. Time-based billing changes how you write the script5. Identity and reconnects6. Connection errors and concurrency7. FAQ

1.One line changes, the rest of the script does not

Playwright and Puppeteer both separate launching a browser from driving one. Locally you call launch, which starts a process and hands back a handle. Against a remote browser you call connectOverCDP or connect with a WebSocket URL, and you get back the same kind of handle.

Everything downstream — newPage, goto, fill, click, waitForSelector, evaluate — behaves identically, because CDP is the protocol the local case uses too. The difference is only whether the socket goes to a process on your machine or across the network.

This is what makes migration cheap for teams that already have working automation. You are not rewriting a script into someone's DSL; you are changing where the browser lives.

Playwright against a remote browser
javascript
123456789101112131415161718192021import { chromium } from "playwright";

const wsUrl =
  "wss://browser.omniscrape.io" +
  `?apikey=${process.env.OMNISCRAPE_KEY}` +
  "&proxy_country=de" +
  "&render_media=false";

const browser = await chromium.connectOverCDP(wsUrl);
try {
  const page = await browser.newPage();
  await page.goto("https://example.com/login");
  await page.fill("#email", process.env.LOGIN_EMAIL);
  await page.fill("#password", process.env.LOGIN_PASSWORD);
  await page.click("button[type=submit]");
  await page.waitForSelector(".dashboard");
  console.log(await page.title());
} finally {
  // The meter runs until this socket closes. Always in finally.
  await browser.close();
}
The same thing in Python
python
123456789101112131415161718import os
from playwright.sync_api import sync_playwright

ws = (
    "wss://browser.omniscrape.io"
    f"?apikey={os.environ['OMNISCRAPE_KEY']}"
    "&proxy_country=us"
    "&render_media=false"
)

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(ws)
    try:
        page = browser.new_page()
        page.goto("https://example.com")
        print(page.title())
    finally:
        browser.close()

2.When a remote browser is the right tool

Use it when you need a browser that stays open across many steps. Full login flows including second-factor prompts, multi-page wizards, anything where later steps depend on state accumulated by earlier ones — these need a live session, not a sequence of independent fetches.

Use it when you already own working automation. A Puppeteer script that has been debugged against a real site represents real knowledge about that site, and moving the browser is far cheaper than reimplementing it as API calls.

Use it when one session should drive many pages. Logging in once and then walking two hundred pages inside that session is both cheaper and more reliable than re-authenticating per page, because the session cookies and the IP stay put.

  • Full login flows, including 2FA prompts
  • Multi-step wizards where state carries between steps
  • Existing Playwright or Puppeteer scripts you do not want to rewrite
  • One authenticated session driving many pages

3.When a per-request API is cheaper

A browser-minute is expensive relative to a fetch. OmniScrape bills BaaS at $0.02 per minute standard and $0.10 per minute with media loaded, while a Web Unlocker request is $0.0035. If all you need is a page's content, holding a browser open to get it is roughly the cost of six requests per minute of connection.

Fetching content, rendering a single-page app to read it, and even basic interactions have cheaper paths. Web Unlocker with mode js_rendering renders SPAs, and js_actions scripts clicks, fills, and waits without a persistent session. Those cover a large share of what people initially reach for a browser to do.

The dividing line is state. If the work is a sequence of independent page reads, per-request billing wins. If the work is one continuous session where step twelve depends on step three, a remote browser wins, and it is not close in either direction.

4.Time-based billing changes how you write the script

The meter starts when the WebSocket connects and stops when it closes. That makes connection lifetime a cost variable in a way that per-request billing never is, and it has a direct consequence: an uncaught exception that leaves the socket open keeps billing until something times out.

So browser.close() belongs in a finally block, always. This is the single most common and most expensive mistake with time-billed browsers — a script that throws on a selector change at 2 a.m. and holds a connection open until morning. The pattern costs one line and removes the entire failure mode.

Keep render_media false unless images genuinely matter to what you are doing. It is a five-fold rate difference — $0.02 against $0.10 per minute — and most automation reads text, fills fields, and clicks controls, none of which need images decoded. Turn it on for visual regression work or when a site gates behaviour on image loading, and leave it off otherwise.

5.Identity and reconnects

Pass session_id on the WebSocket URL to pin the same residential IP across reconnects. This matters for flows that survive a dropped connection: without it, reconnecting lands you on a different IP, which invalidates anti-bot cookies bound to the previous one and typically forces you back through a challenge or a login.

Use proxy_country to match the market whose content you need. Sites frequently serve different catalogues, pricing, and availability by geography, and a session in the wrong country returns a coherent page with the wrong data — a failure that passes every technical check you have.

Treat one session as one logical unit of work. Log in, do the work, close. Holding a connection open between units to avoid re-authenticating is a false economy on a per-minute meter — idle connection time bills at exactly the same rate as working time.

6.Connection errors and concurrency

Failures surface as a rejected WebSocket upgrade rather than as a page error, so the message from Playwright or Puppeteer is what carries the status code. A 401 means the apikey is missing or invalid. A 402 means balance is too low, the trial expired, or the plan's concurrent browser limit was reached. A 429 means the concurrency limit specifically, and should be retried with backoff.

Concurrent browsers are capped by plan, the same way scrape concurrency is. A worker pool sized to your plan is more predictable than opening connections optimistically and handling rejections, because every rejected connection still costs you a round trip and a retry decision.

Wrap connection establishment in the same retry logic you would use for any network call, and make sure the retry path cannot leave an earlier socket open. A retry loop that reconnects without closing the previous attempt is the second most expensive mistake in this category.

Frequently asked questions

Do I have to rewrite my Playwright script?

No. Replace chromium.launch with chromium.connectOverCDP and a WebSocket URL — or puppeteer.launch with puppeteer.connect and browserWSEndpoint. Everything after that line is unchanged, because CDP is the same protocol the local case uses.

When is Web Unlocker cheaper than a remote browser?

Whenever the work is stateless page reads. A request is $0.0035 against $0.02 per browser-minute, and mode js_rendering renders SPAs while js_actions handles clicks and fills. Reach for a browser when later steps depend on state from earlier ones.

Why does my bill look higher than the time I spent working?

Almost always a socket left open by an uncaught error. The meter runs from connect to close, so a script that throws before browser.close() keeps billing. Put the close in a finally block — that one line removes the failure mode entirely.

What does render_media actually change?

It loads images, fonts, and video, and moves the rate from $0.02 to $0.10 per minute. Most automation reads text and clicks controls, none of which needs images decoded, so leave it off unless you are doing visual work or the site gates behaviour on image loading.

How do I keep the same IP if my connection drops?

Pass session_id on the WebSocket URL. It pins the same residential IP across reconnects, so anti-bot cookies bound to that IP survive the reconnect instead of forcing you back through a challenge or a login.

Related guides

  • Puppeteer Web Scraping: Patterns, Anti-Bot Limits, and BaaS Integration
  • Playwright Web Scraping: Practical Patterns for Protected Sites
  • Headless Browser Scraping: When to Use It and How to Do It Right
  • Selenium Web Scraping: Practical Patterns for Real-World Projects
  • Scrapy Web Scraping with OmniScrape: Download Middleware, Pipelines, and Scale
  • Beautiful Soup Web Scraping: A Practical Guide

Ready to scrape without blocks?

Published unit rates, clear plan limits, and billing tied to successful work. No hidden fees and no guessing what a scrape will cost. Free trial credit on signup. No credit card required.

Ready to get started?

Start scraping protected sites today. No credit card required.

OmniScrapeOmniScrape

Web scraping infrastructure for developers. One API call to bypass any protection.

All systems operational

Payments accepted

Credit / Debit CardVisaMastercardCryptoBTCUSDTETH50+ coins

Product

  • Web Unlocker
  • Browser-as-a-Service
  • Residential Proxies
  • Pricing

Developers

  • API Reference ↗
  • Quickstart ↗
  • All Guides
  • Use Cases
  • Status

Company

  • About
  • Contact

Legal

  • Privacy Policy
  • Terms of Service
  • Refund Policy
  • Cookie Policy
  • Acceptable Use

Popular guides

  • How to Bypass Cloudflare When Web Scraping
  • How to Bypass DataDome When Web Scraping
  • How to Bypass Akamai Bot Manager When Web Scraping
  • How to Bypass F5 BIG-IP Bot Defense When Web Scraping
  • Web Scraping API: Endpoint, Modes, Output Formats & Integration Patterns
  • Web Scraping with Python
  • Scrape JavaScript-Rendered Pages: SPAs, Hydration, and Hidden APIs
  • Web Scraping Without Getting Blocked
  • Headless Browser Scraping: When to Use It and How to Do It Right
  • Rotating Proxies for Web Scraping: Policies, Session Binding, and Geo Pools
  • OmniScrape vs ScrapingBee
  • OmniScrape vs ZenRows
Browse all guides →

OmniScrape is a product of PT Data Digital Grup. Copyright ©2026.

PrivacyTermsRefundsAcceptable Use