资讯动态

CDP开启方法详解:从调试端口到VBA实战,掌握浏览器自动化的万能钥匙

发布时间:2026/9/19 3:00:04 来源:尧图企业网站定制
搞浏览器自动化绕不开一个词CDP全称 Chrome DevTools Protocol也就是 Chrome 开发者工具协议。它就是 Chrome 给开发者留的一条直通浏览器内部的控制通道通过 WebSocket 端口和 JSON 消息外部程序可以指挥 Chrome 做很多事情打开链接、模拟点击、读取页面数据、拦截网络请求、截图录屏连 DevTools 面板里的功能都能直接调用。花 10 分钟把 CDP 的开启方法吃透后面做任何浏览器自动化都等于多了一把万能钥匙。这篇文章不是讲枯燥的协议文档而是把你从“听说过 CDP”带到“能实际用起来”这一步。内容既适用于爬虫工程师、自动化测试开发也适合那些整天对着 Excel、想用 VBA 操控 Chrome 的办公族。只要按步骤操作基本都能跑通。1. CDP 是个什么玩意儿为什么值得开1.1 CDP 的运行机制CDP 不是一个独立的软件也不是插件它内嵌在 Chrome 浏览器里。你按下 F12 看到的开发者工具DevTools本质上是 Chrome 内置的一个 CDP 客户端它和浏览器内核之间走的正是 CDP 协议。我们做自动化时要做的事情就是绕过 DevTools 的图形界面直接用代码搭建一个客户端连上同一个调试通道。整个通信过程是这样的Chrome 启动时如果带上了--remote-debugging-port参数它就会在本机监听一个 TCP 端口对外提供两个能力。一是 HTTP 接口用来查询浏览器状态、获取页面列表二是 WebSocket 服务真正的控制指令通过 WebSocket 连接发送消息格式是 JSON-RPC 风格的请求和响应。浏览器里每一个可调试对象都叫一个 target页面是 page 类型扩展后台是 background_page 类型你往 WebSocket 发一条命令Chrome 处理后把结果返回这就是一次完整的 CDP 交互。另一个容易混淆的点是 target 的分层。通过 HTTP 接口/json/version拿到的webSocketDebuggerUrl通常指向 browser 级别的 target它能控制整个浏览器比如创建新标签页、监听所有页面的网络请求。而/json接口返回的是 page 级别的 target每个 WebSocket 地址只能控制对应的那一个页面。刚开始用的人容易在这里犯迷糊连了 page 的地址想创建新页面发现没这个命令可用。记住一个原则就够了做全局操作选 browser 级别做页面操作选 page 级别。1.2 CDP 能解决什么实际问题我最早接触 CDP 是在做一个 Excel 表格的自动填单需求——需要在网页里逐条录入数据但当时的办公电脑上只有 Windows 和 Office连 Python 都没有。有同事提议用按键精灵但那个稳定性实在不行。后来换成 CDP 直接和 Chrome 对话流程瞬间变得干净了启动浏览器、打开指定页面、执行 JS 填写表单、读取结果全部是标准接口不用猜坐标、不用调窗口焦点。CDP 的实际用途远不止这一种。我们团队后来用它做过接口抓包监听 Network 域的请求和响应事件把页面发出的 API 调用全部记录下来这在某些网站用 Selenium 很难做到因为 Selenium 的 WebDriver 协议对网络层事件的支持非常有限。还做过性能分析通过 Performance 域的接口拿到页面加载的详细耗时数据给前端同事排查问题很管用。再比如你现在打开这篇文章用的浏览器如果能看到 DevTools 面板里的远程调试目标列表其实背后也是 CDP 在驱动。1.3 为什么人人都在喊 CDP它和 Selenium/Puppeteer 是什么关系如果你用过 Puppeteer、Playwright或者听说过 Selenium那么 CDP 对你来说应该不陌生。Puppeteer 和 Playwright 的底层就是 CDP它们把协议封装成了好用的高级 API省去你自己发 JSON 消息的麻烦。Selenium 4 之后也内置了 CDP 支持可以绕过 WebDriver 直接调用 Chrome 的底层能力。那为什么不直接推荐你用 Puppeteer还要自己学 CDP因为封装是方便也意味着多了一层不确定。实际工作中常遇到这种情况某个页面只有通过 CDP 的原生命令才能拿到关键数据或者 Puppeteer 的某个方法在特定版本上报错这时候你必须回到协议层去手动调试。另外很多场景下你根本用不了 Node 或 Python——比如 VBA 的环境要操控 Chrome 就只有 CDP 这一条路可走。所以我的建议是不管平时用不用封装库都花点时间把 CDP 的开启和基础调用搞明白工具可以换协议层的能力是通用的。2. 开启 CDP两种启动方式实测对比2.1 命令行启动参数一条命令搞定调试端口开启 CDP 最直接的方式就是给 Chrome 添加启动参数。在命令行里执行下面这条命令Windows 示例C:\Program Files\Google\Chrome\Application\chrome.exe --remote-debugging-port9222 --user-data-dirD:\chrome-debug这里有两个关键参数缺一不可。--remote-debugging-port9222指定调试端口端口可以自己换比如 9333、9555 都可以只要没被占用。--user-data-dir指定一个独立的用户数据目录这一点非常重要。Chrome 的机制是如果已经有一个普通 Chrome 实例在运行你再执行不带参数的 chrome.exe它只会把请求转发给现有实例而不是新开一个进程即使你加了 --remote-debugging-port 参数如果没有指定不同的 user-data-dir浏览器依然会复用已有实例调试端口根本不会生效。这也是网上教程里最常见的翻车点明明执行了带参数的命令打开 http://localhost:9222 却看不到任何内容十有八九是 user-data-dir 没设置或者设成了和当前浏览器一样的目录。单独准备一个调试用的用户目录既能让调试端口稳定生效也能避免自动化操作污染日常浏览的登录状态和书签数据。macOS 上启动方式略有不同。因为系统会拦截普通的 open 命令传参正确写法是open -na Google Chrome --args --remote-debugging-port9222 --user-data-dir/tmp/chrome-debugLinux 下比较简单直接执行 chrome 或 chromium 加上同样的参数即可。启动之后打开 http://localhost:9222/json/version 验证一下能看到类似下面的 JSON 就说明 CDP 已经通了{ Browser: Chrome/120.0.6099.129, Protocol-Version: 1.3, User-Agent: Mozilla/5.0 ..., webSocketDebuggerUrl: ws://localhost:9222/devtools/browser/xxx }返回里最重要的字段就是webSocketDebuggerUrl一会连接的时候要用它。如果访问不了按后面的排查章节对照处理。2.2 用代码动态启动 Chrome适合自动化流程的动态开关手动敲命令适合临时调试但如果要写进自动化程序调试端口的开启和 Chrome 进程的启停都应该交给代码控制。下面是一个 Python 例子它负责启动一个带调试端口的 Chrome 实例并且等待端口就绪import subprocess import time import json import urllib.request chrome_path rC:\Program Files\Google\Chrome\Application\chrome.exe profile_dir rD:\chrome-cdp-profile proc subprocess.Popen([ chrome_path, --remote-debugging-port9222, f--user-data-dir{profile_dir}, --no-first-run, # 跳过首次运行向导 --no-default-browser-check, # 不检查默认浏览器 ]) # 轮询等待 CDP 端口可用 for _ in range(20): try: with urllib.request.urlopen( http://localhost:9222/json/version, timeout1 ) as resp: info json.loads(resp.read().decode()) print(CDP ready:, info[webSocketDebuggerUrl]) break except Exception: time.sleep(0.5) else: print(Chrome CDP 启动失败请检查参数或端口占用)注意里面加了--no-first-run和--no-default-browser-check目的是避免 Chrome 首次启动时弹出一堆引导页面和确认框干扰自动化流程。进程用完以后调用proc.terminate()关闭即可。如果你用的是 MacPython 代码里启动命令要相应替换成open -na Google Chrome --args ...的形式Linux 则直接写浏览器可执行文件路径。这种方式的好处是整个过程完全由代码掌控测试结束了关闭进程下次再启动又是干净状态。配合固定用户目录还能在多次运行之间保留登录状态适合需要登录后才执行的操作。3. 连接 CDP握手、命令与高频接口3.1 拿到 WebSocket 调试地址CDP 开启之后真正的控制是通过 WebSocket 完成的。首先要从 HTTP 接口拿到可用的连接地址。根据你的需求有两个接口比较常用http://localhost:9222/json/version浏览器级别的信息返回的 webSocketDebuggerUrl 指向 browser target可控制整个浏览器。http://localhost:9222/json当前所有可调试目标的列表里面每个 target 有自己的 webSocketDebuggerUrl指向具体的 page、worker 等。在 Python 里可以这样获取 page 级别的地址import json import urllib.request with urllib.request.urlopen(http://localhost:9222/json) as resp: targets json.loads(resp.read().decode()) # 取第一个 page 类型的 target for t in targets: if t[type] page: print(页面标题:, t[title]) print(页面URL:, t[url]) print(调试地址:, t[webSocketDebuggerUrl]) break这里要注意target 列表里可能包含其他类型比如扩展进程、Service Worker 等。要控制页面就筛选 type 为 page 的 target要直接创建新标签页可以用 browser 级别的 WebSocket 发送 Target.createTarget 命令或者直接用 HTTP 接口PUT /json/new?url...这个细节后面实战部分会讲到。补充一个点Chrome 的/json/version返回的 webSocketDebuggerUrl 不一定是固定的尤其在一个浏览器进程里开过多轮调试时会变化。所以每次连接前都实时拉一次接口不要硬编码地址否则一旦地址失效你就要排查老半天。3.2 发送第一条 CDP 命令拿到 WebSocket 地址之后就可以建立连接了。CDP 的消息格式是 JSON-RPC 2.0 风格核心结构就是id、method、params。id 是递增的序号用来对应响应method 是命令名params 是命令需要的参数。下面是一个最简单的例子用 Python 的 websocket-client 库连接 page target并执行一段 JavaScriptimport json import websocket ws_url ws://localhost:9222/devtools/page/xxx # 换成实际地址 ws websocket.create_connection(ws_url, timeout10) # 发送 Runtime.evaluate 命令在页面里执行 JS ws.send(json.dumps({ id: 1, method: Runtime.evaluate, params: { expression: document.title } })) # 接收响应 resp json.loads(ws.recv()) print(resp[result][result][value])如果成功返回的 value 就是当前页面的标题。注意 Runtime.evaluate 返回的结构是多层的最外层有 result表示命令是否执行成功里面的 result 才是真正要拿的数据type 表示返回值的类型value 是具体值。如果命令执行出错响应里会有 error 字段错误信息通常能看到方法名写错、参数缺失或者页面已经关闭。WebSocket 连接成功之后它不仅是“发命令-收响应”的单一通道Chrome 还会主动往连接里推送事件消息。事件消息没有 id字段里包含 method 和 params。比如监听Network.requestWillBeSent页面上发出的每个网络请求都会实时推送过来。所以你的接收逻辑一定要区分有 id 的消息是命令的响应没有 id 的消息是事件推送两者都要处理。3.3 高频命令速查表实际使用中以下这些 CDP 命令出现频率最高先背熟这些基本就够应付 80% 的日常自动化需求了命令用途Page.navigate导航到指定 URL参数是 urlPage.reload刷新当前页面可设置 ignoreCache 跳过缓存Runtime.evaluate在页面上下文执行 JS返回结果或抛出异常Runtime.evaluateawaitPromise执行异步 JS 并等待 Promise 结果Network.enable开启网络监控后续可以收到请求和响应事件Network.getResponseBody获取某条网络请求的响应体参数是 requestIdPage.captureScreenshot截取当前页面视图返回 base64 图片Emulation.setDeviceMetricsOverride模拟移动端窗口大小和设备参数Target.createTarget在浏览器级别创建一个新页面参数是 urlTarget.closeTarget关闭指定 target参数是 targetId命令的详细参数都可以在 Chrome 官方协议文档里查到直接搜 Chrome DevTools Protocol 就能找到。需要重点提醒的是不同 Chrome 版本的协议命令略有差异个别新命令在旧版本里不存在。所以生产环境里最好固定 Chrome 版本或者用 Chrome for Testing 这类版本可固定的浏览器避免因为自动升级导致协议字段对不上。4. 实战案例VBA 通过 CDP 操控 Chrome4.1 VBA 场景下的 CDP 优势选择 VBA 作为例子是因为这个需求太真实了。很多做运营、财务、行政的同学日常工作大量在 Excel 和网页系统之间切换手动录入数据又慢又容易出错。他们不一定有 Python 环境但一定能用 Excel 的 VBA。传统的方案是用 Selenium 的 VBA 封装库或者 IE 控件前者安装配置麻烦后者基本已经淘汰。直接用 CDP 就清爽多了——Chrome 自带协议不需要额外驱动程序VBA 只要能发 HTTP 请求就可以指挥 Chrome。VBA 里做 CDP 控制一定要分清哪些操作走 HTTP 接口、哪些操作必须走 WebSocket这个判断直接决定方案复杂度。最简单的场景比如打开一个指定网页、关闭某个标签页这些用 HTTP 接口就够了。Chrome 的调试端口提供了/json/new和/json/close/{targetId}这样的 REST 风格接口VBA 用 MSXML2.XMLHTTP 对象就能请求连 WebSocket 都不需要碰。页面控制、执行 JS、抓取请求这类复杂操作则必须走 WebSocket这时候建议用桥接方案让 VBA 调用本地的 Python 或 PowerShell 脚本去完成 WebSocket 通信。4.2 方案A用 HTTP 接口做轻量操作下面这段 VBA 代码作用是让已经开启了 CDP 的 Chrome 打开一个新标签页并跳转到指定网址Sub OpenTabViaCDP() Dim http As Object Dim json As String Set http CreateObject(MSXML2.XMLHTTP) 新版 Chrome 对 /json/new 使用 PUT 请求旧版用 GET http.Open PUT, http://localhost:9222/json/new?https://www.example.com, False http.send If http.Status 200 Then json http.responseText Debug.Print json 返回的 JSON 里包含新标签页的 id 和 webSocketDebuggerUrl Else Debug.Print 请求失败状态码 http.Status 常见原因Chrome 没有带 --remote-debugging-port 启动或端口错误 End If End Sub这个方法简单到不能再简单但它已经能覆盖“VBA 一键打开网页”这类需求了。你要是想把新标签页关闭可以先通过http://localhost:9222/json拿到所有 targetId然后用http.Open PUT, http://localhost:9222/json/close/ targetId关闭。整个过程不需要 WebSocket兼容性也好因为走的是最稳定的 REST 接口。但注意/json/new这个方法在不同 Chrome 版本里的动词不一样。老版本支持GET /json/new?urlxxx新版本改成了 PUT。为了兼容我一般先试 GET如果返回 405 再改用 PUT或者直接写一个小容错逻辑把两种请求都试一遍取成功的那个返回。这种小坑反复踩几次就有经验了。4.3 方案B桥接 Python/PowerShell 完成复杂控制如果你需要在页面里执行 JS、读取渲染后的数据、拦截网络请求那 HTTP 接口就不够用了必须走 WebSocket。可是 VBA 没有内置的 WebSocket 类直接在 VBA 里实现 WebSocket 协议又过于痛苦。实用的做法叫“桥接”——VBA 负责业务逻辑和界面交互把要执行的 CDP 命令通过 Shell 传给一个本地脚本脚本负责和 Chrome 建立 WebSocket 并返回结果。优先推荐用 Python 做桥接层因为 Python 有很成熟的 websocket-client 库。在 Excel 的 VBA 里通过 Shell 调用 Python 脚本把参数传进去Sub ExecJavaScriptByBridge() Dim scriptPath As String Dim expression As String Dim shellCmd As String scriptPath D:\scripts\cdp_exec.py expression document.querySelector(h1).innerText shellCmd python scriptPath --method Runtime.evaluate --expression expression Dim result As String result RunCommand(shellCmd) Debug.Print result End Sub Function RunCommand(cmd As String) As String Dim shell As Object Set shell CreateObject(WScript.Shell) 用临时文件捕获脚本输出避免控制台中文乱码 Dim tmpFile As String tmpFile Environ(TEMP) \cdp_out.txt shell.Run cmd /c cmd tmpFile , 0, True Dim fso As Object Set fso CreateObject(Scripting.FileSystemObject) RunCommand fso.OpenTextFile(tmpFile, 1).ReadAll End Function对应的 Python 脚本 cdp_exec.py核心逻辑大致是这样import argparse import json import urllib.request import websocket parser argparse.ArgumentParser() parser.add_argument(--method, requiredTrue) parser.add_argument(--expression, default) args parser.parse_args() # 获取当前第一个 page target 的 WebSocket 地址 with urllib.request.urlopen(http://localhost:9222/json) as resp: targets json.loads(resp.read().decode()) page next(t for t in targets if t[type] page) ws websocket.create_connection(page[webSocketDebuggerUrl]) ws.send(json.dumps({ id: 1, method: args.method, params: {expression: args.expression} })) resp json.loads(ws.recv()) print(json.dumps(resp, ensure_asciiFalse)) ws.close()这段代码虽然简单却解决了很多实际需求。某些网页内容是在 JS 里异步渲染出来的你用 Excel 直接读取网页源码根本拿不到但通过 Runtime.evaluate 去查询 document 里的实际 DOM就能拿到渲染后的数据。如果办公电脑上没有 Python 环境也可以让 VBA 调用 PowerShell 的 ClientWebSocket 类来做同样的事情。PowerShell 脚本里可以这样建立连接$ws [System.Net.WebSockets.ClientWebSocket]::new() $ct [System.Threading.CancellationToken]::None $ws.ConnectAsync([Uri]ws://localhost:9222/devtools/page/xxx, $ct).Wait() $payload { id 1; method Runtime.evaluate; params { expression 11 } } | ConvertTo-Json -Compress $bytes [System.Text.Encoding]::UTF8.GetBytes($payload) $segment [ArraySegment[byte]]::new($bytes) $ws.SendAsync($segment, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $ct).Wait() # 接收响应 $buffer New-Object byte[] 65536 $segmentRecv [ArraySegment[byte]]::new($buffer) $result $ws.ReceiveAsync($segmentRecv, $ct).Result $text [System.Text.Encoding]::UTF8.GetString($buffer, 0, $result.Count) Write-Output $text $ws.Dispose()桥接方案的实现细节千变万化但核心思路只有一个把 WebSocket 通信这个脏活交给擅长它的语言VBA 只负责发号施令和接收结果。这样既有 CDP 的强大能力又保持了 VBA 的易用和灵活。4.4 完整示例一键打开网页并执行 JS把上面两个方案组合起来就可以实现一个比较完整的 VBA 场景在 Excel 里输入网址点击按钮Chrome 打开网页并执行一段 JS把结果带回 Excel。完整流程是用 Shell 启动带--remote-debugging-port9222的 Chrome。VBA 用 XMLHTTP 请求/json/new创建新标签页。如果有复杂 JS 需要执行就调用桥接脚本走 WebSocket。用/json接口查询页面标题或 URL 验证结果。在这个流程里第 1 步的启动命令可以放在 Excel 的 Workbook_Open 事件里也可以点击按钮时再启动。注意启动前先判断 9222 端口是否已经可用如果之前有残留进程直接复用即可不用重复启动浏览器。这样一个 Excel 工具就能做很多事情了批量查快递、批量填报表、自动读取网页上的行情数据底层都是这套“VBA CDP”的组合。只要网页本身没有做太严格的自动化检测很多日常工作都可以在这套方案上跑起来。5. 开启 CDP 的常见坑与排查实录5.1 端口失效90% 是你没指定用户目录这个问题我在前面反复强调但还是要单独拿出来说因为它在实际需求里出现频率太高了。表现是命令执行了Chrome 也打开了但访问 http://localhost:9222/json/version 就是一片空白或者干脆是错误页面。原因几乎都是同一个Chrome 已经在跑一个普通的浏览器实例你第二次执行的命令被“合并”进了现有进程新参数根本没人理。解决办法就是使用独立用户目录--user-data-dir指向一个新目录或者至少和现有 Chrome 使用的目录不同。有时候你以为自己指定了目录但路径写错比如文件夹不存在Chrome 会启动失败或者退回默认配置这时候可以看一眼进程命令行确认参数有没有真正传到 ChromeWindows 的任务管理器里勾选“命令行”列macOS 上可以用ps aux | grep chrome检查。5.2 连接被拒WebSocket 层的问题端口通了HTTP 接口能访问但 Python 或 PowerShell 连接 WebSocket 时总是报 403、404 或者超时。403 通常是浏览器拒绝了这个来源常见原因是你能访问的 webSocketDebuggerUrl 是用 localhost 生成的但代码里改成了 127.0.0.1 或者局域网 IP两者在 Chrome 的校验逻辑里并不总是一致解决方案是把 ws 地址原样拿来使用千万不要自己拼接。404 一般是 target 已经关闭页面被手动关掉对应的 WebSocket 端点自然就失效了重新拉取/json接口拿到新的地址再连。超时则可能是防火墙拦截了 WebSocket 的升级请求把端口放行或者代码里把连接超时时间设长一点。还有一个容易忽略的点CDP 的 WebSocket 地址是ws://开头不是wss://有些人复制地址时被前端页面的 https 协议混淆把 ws 写成了 wss也会导致连接失败。5.3 版本差异老 Chrome 与新协议Chrome 版本差异带来的坑在 CDP 使用中很常见。比如/json/new从 GET 变成了 PUT像前面说的最好做兼容处理。再比如一些命令的参数格式在不同年份的版本里有调整最常见的是 Performance、Network 域里某些字段重命名。我的经验是如果项目长期要用 CDP就在固定环境里锁死 Chrome 版本不要让它自动升级或者用 Chrome for Testing 这类明确为自动化设计的版本每次构建时固定同一个版本号能减少大量莫名其妙的兼容问题。同时浏览器版本太老也有问题。比如 Windows 7 上最后能用的 Chrome 109它的 CDP 协议还停留在比较早的版本很多新命令不可用连接方式上也有一些细微差别。如果一定要在老系统上做自动化建议提前用/json/version返回的 Protocol-Version 字段和官方协议文档对照确认你要用的命令确实存在。5.4 安全与并发建议开启 CDP 调试端口等于把你的浏览器控制权开放给了能够访问到这个端口的所有程序。默认情况下它只监听 localhost所以只要你的电脑没有被入侵风险是可控的。但如果你为了图方便把端口绑定到了 0.0.0.0或者在生产服务器上开启了远程调试那就等于把浏览器裸奔在外面了任何人都可以通过 CDP 控制它、读取页面内容、执行任意 JS。所以两条硬性建议一调试端口只留给本机使用不监听外网地址二用完就关不要长期挂着。个人测试时还可以考虑--remote-debugging-pipe参数用管道替代 TCP 端口安全性更高但只有 Windows 和 Linux 支持使用也比端口模式复杂。并发方面一个 CDP 端口下可以挂多个 WebSocket 连接但一个连接同一时间最好只被一个逻辑任务使用避免消息 id 混乱导致响应错乱。如果你的自动化脚本需要并行处理多个页面建议用 browser 级别的 target 统一管理每个页面独立建立连接任务之间不要共享同一个 WebSocket。还可以通过启动多个 Chrome 实例、每个实例用不同端口来彻底隔离环境这是最省心的并发方案。最后说点实际操作中的体会。用 CDP 这几年我最大的感受是把启动参数和用户目录的搭配固定成一套模板之后后面基本不会再出问题。我现在几乎就是把 Chrome 调试模式做成了一个独立快捷方式放在一台只跑自动化的机器上需要时通过 HTTP 接口去开标签页、通过桥接脚本去执行 JS用起来和本地服务一样顺手。还有一个建议第一次接触 CDP 的人不要急着封装库先手动走一遍启动、查接口、连 WebSocket、发命令、收响应的完整流程对协议的理解会扎实很多。后面不管你是用 Puppeteer、Selenium 还是自研框架遇到问题都能快速定位到协议层。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价