We've just released the Zyte CDP browser, so this post covers what it is, what it's good for, and, just as important, when you shouldn't use it. If you've got existing Playwright or Puppeteer scripts and you're tired of babysitting browser infrastructure, this is for you.
What is CDP?
CDP is the Chrome DevTools Protocol, a way of connecting to and controlling a running browser, and it's neither new nor exotic. Chrome DevTools itself uses this protocol every time you inspect network requests or run JavaScript in the console on an open page.
As web scrapers, we rarely touch CDP directly - instead we use Playwright or Puppeteer as a wrapper around the underlying CDP commands. I'll stick to Playwright here, and through it we can launch browsers, connect to them, run actions on pages, and everything else the job needs.
The problem with local browsers
The standard workflow looks like this: your script downloads a Chrome binary, launches it, and connects to it. This works fine on your laptop, but it falls apart the moment you need scale.
Browsers are resource-heavy, they eat memory, and running them in any real volume is an infrastructure project in its own right. You end up managing containers, memory limits, zombie processes, and crash recovery instead of writing scrapers, and if your scrapers already run in Docker or Kubernetes, the browser pods are the biggest and least reliable things in the cluster.
The fix is to run the browsers somewhere else, connecting to a remote browser on separate infrastructure while keeping your scripts exactly as they are. That's what the Zyte CDP browser is, browsers running on our infrastructure with your Playwright scripts driving them over CDP.
You also get our web scraping technology bundled in, so ban solving and fingerprinting come with the browser and you're not solving those problems yourself either.
An important distinction before you start
If all you need is rendered HTML and you don't have something already build, don't use this, use browserHtml with Zyte API instead. It's simpler and it's the right tool for that job.
The CDP browser makes sense when you have a Playwright script that performs many different actions, needs fine control over the browser, or runs through a flow where state has to carry from one step to the next. Zyte API actions can already handle some of this, but they're much more limited, and direct CDP access removes those limits.
Why we built it
This didn't come out of a product roadmap meeting, it came out of a run of customer conversations where the same few problems kept coming up.
Code nobody wanted to rewrite. One team scraping real estate listings had built their whole dynamic pipeline on Playwright and Puppeteer, and moving to Zyte API meant translating every one of those scripts into API calls or the Actions model. They looked at the size of that job and didn't want to do it, which is fair enough. With a CDP endpoint the scripts stay as they are, the connection line changes and nothing else does.
A second provider. A job-market data startup was already running on another vendor's hosted browser over CDP, and it was failing outright on certain drag-and-drop challenges. They didn't want to switch, they wanted a fallback for when the first option broke, and since the interface is CDP either way, swapping providers is a config change. They also wanted the browser to work out which proxy tier a site needs on its own, datacenter by default and residential only when the site forces it, which is how Zyte API already handles bans and the CDP browser inherits it.
Their sessions never needed to live longer than five minutes, by the way, they just wanted to connect, extract, and disconnect. Not every CDP use case is a long-running one.
Smart Proxy Manager users. SPM supported browser automation, and Zyte API's proxy mode didn't cover everything those users relied on, so with SPM being retired in 2026, people running Puppeteer or Playwright through it needed somewhere to go that didn't mean a rewrite. The CDP browser is that.
Fingerprinting. Websites look at hardware footprint, mouse movement, and keyboard timing, and a stock headless script advertises itself as a bot and gets banned on the first request. Running a customised browser on our side, with our fingerprint and IP handling behind it, takes that maintenance off your plate. I'm not going to claim it gets you everything, but it does mean the browser isn't the weakest link in your stack anymore.
Connecting Playwright to the Zyte browser
Here's the simplest possible example, where you connect with a CDP connection string and authenticate with your Zyte API key:
1import asyncio
2from base64 import b64encode
3from playwright.async_api import async_playwright
4
5ZYTE_API_KEY = "your-api-key"
6
7CDP_URL = "wss://browser.zyte.com" # verify against docs
8AUTH = b64encode(f"{ZYTE_API_KEY}:".encode()).decode()
9
10async def main():
11 async with async_playwright() as p:
12 browser = await p.chromium.connect_over_cdp(
13 CDP_URL,
14 headers={"Authorization": f"Basic {AUTH}"},
15 )
16 page = await browser.new_page()
17 await page.goto("https://example.com")
18 print(await page.title())
19 await browser.close()
20
21asyncio.run(main())That's it. connect_over_cdp is a standard Playwright method, and if you check its docstring it describes connecting via the Chrome DevTools Protocol, which is exactly what's happening here. Puppeteer's puppeteer.connect takes the same kind of websocket endpoint if that's your tool.
If you already have a Playwright script, this connection block is the only part you change to move from a local or other remote browser onto our infrastructure, and everything after the connection stays the same.
Interactions are where it gets useful
Rendering a page over CDP works, but the value really shows up when your script interacts with pages, because page.click, page.fill, waiting for selectors, anything Playwright exposes, all of it runs against the remote browser:
1page = await browser.new_page()
2await page.goto("https://example.com/search")
3
4await page.fill("#search-input", "web scraping")
5await page.click("#search-button")
6await page.wait_for_selector(".results")
7
8data = await page.locator(".results .item").all_text_contents()The script connects to our infrastructure, runs the whole sequence, and returns the data, and the more complicated your actions, the more this pays off compared to expressing everything through API action definitions.
Blocking images to cut data transfer
One thing I do a lot is block images, since they're usually dead weight in a scraping job and blocking them saves a surprising amount of data transfer and loading time:
1await page.route(
2 "**/*.{png,jpg,jpeg,gif,webp,svg}",
3 lambda route: route.abort(),
4)It's one line, and worth doing by default unless you need the images.
Screenshots
Playwright's page.screenshot runs against the remote browser like everything else:
1screenshot = await page.screenshot(full_page=True)
2with open("page.png", "wb") as f:
3 f.write(screenshot)Raw CDP commands
Playwright covers most needs, but a direct CDP session gives you access to browser internals that Playwright doesn't wrap, such as overriding the browser's timezone:
1cdp = await page.context.new_cdp_session(page)
2await cdp.send("Emulation.setTimezoneOverride", {
3 "timezoneId": "America/New_York",
4})You can change a lot about the browser this way, and this level of control is the reason to have a direct CDP connection at all, since a request/response API can't give it to you.
Using it with Scrapy
If you're working in Scrapy, the scrapy-playwright extension normally downloads and launches a local browser, which is exactly the pattern I warned about at the start, so point it at our infrastructure with the CDP URL instead:
1# settings.py
2PLAYWRIGHT_CDP_URL = "wss://browser.zyte.com" # verify against docs
3
4DOWNLOAD_HANDLERS = {
5 "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
6 "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
7}If you have existing Playwright scripts doing complicated actions and you want off local browsers, in a lot of cases it really is this straightforward, you add the CDP URL and the work moves to a remote browser.
Giving AI agents browser access
Claude wrote all the example code in this post's companion video, and that's the point here, because if you give an agent the CDP connection string and an API key, it can write its own Playwright scripts for a task and run them against a remote browser.
That means the machine your agent runs on can be tiny, since it doesn't need to run browsers itself when we handle that off-site. The agent writes code, the browser lives on our infrastructure, and you skip the whole problem of provisioning browser-capable compute for agent workloads.
When to use it, when not to
Use the CDP browser when:
- You have existing Playwright scripts with many different actions
- You need fine control over the browser, including raw CDP commands
- Your flow spans several steps and the site has to remember state between them, such as a solved challenge, a login, or a filled form
- You're moving off Smart Proxy Manager or another hosted browser and don't want to rewrite anything
- You want agents to drive real browsers without hosting them
Skip it when you just need rendered HTML, because Zyte API with browserHtml is the better option there.
Links to pricing, access, and the community are below, and if you build something with this, come tell us what you used it for.











