Python-Chrome-CDP入门
用 Python 调用 Chrome DevTools Protocol,从打开页面到截图
Chrome DevTools Protocol(CDP)是 Chrome 暴露出来的一套调试接口。平时按 F12 打开开发者工具,看到的 DOM、网络请求、性能数据,底层大多走的这套协议。用 Python 直接连上去,可以控制浏览器、执行 JS、截屏,也能拿到比 Selenium 更细的运行时信息。
这篇用一个最小例子走一遍:启动无头 Chrome,连上 CDP,打开一个空白页,改点内容,再截张图。
先把 Chrome 的调试端口打开
Chrome 启动时加一个参数,就会在本地开一个调试端口:
chrome --headless=new --remote-debugging-port=9222 --remote-allow-origins=* --user-data-dir=./chrome-profile about:blank
几个参数值得说一下。--headless=new 是无头模式,Chrome 112 之后用这个写法;老版本直接写 --headless。--user-data-dir 指定一个临时用户目录,免得和你平时用的浏览器配置打架。--remote-allow-origins=* 是给非浏览器客户端用的,Python 的 websocket 库连接时会带 Origin 头,不加这个参数,Chrome 可能直接返回 403。
端口起来后,访问本地的 /json/version 能看到浏览器版本和 websocket 地址,/json/list 则列出当前打开的页面。每个页面都有一个 webSocketDebuggerUrl,后面的命令都发到这个地址。
Python 这边要装什么
只需要两个库:
pip install requests websocket-client
requests 用来查 HTTP 接口,websocket-client 用来建长连接。Playwright、Selenium 这些工具把 CDP 包了一层,用起来更省事;这篇直接用原生协议,目的是看清楚消息是怎么发的。
一个能跑的最小例子
import base64
import json
import subprocess
import time
import requests
import websocket
CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
HOST = "localhost"
PORT = 9222
SCHEME = "http"
BASE = f"{SCHEME}://{HOST}:{PORT}"
proc = subprocess.Popen([
CHROME,
"--headless=new",
f"--remote-debugging-port={PORT}",
"--remote-allow-origins=*",
"--user-data-dir=./chrome-profile",
"about:blank",
])
for _ in range(50):
try:
requests.get(f"{BASE}/json/version", timeout=0.5)
break
except requests.RequestException:
time.sleep(0.1)
else:
raise RuntimeError("Chrome 没有在预期时间内启动")
targets = requests.get(f"{BASE}/json/list").json()
page = next(t for t in targets if t["type"] == "page")
ws = websocket.create_connection(page["webSocketDebuggerUrl"])
msg_id = 0
def call(method, params=None):
global msg_id
msg_id += 1
ws.send(json.dumps({"id": msg_id, "method": method, "params": params or {}}))
while True:
msg = json.loads(ws.recv())
if msg.get("id") == msg_id:
return msg
call("Page.enable")
call("Runtime.enable")
call("Page.navigate", {"url": "about:blank"})
time.sleep(0.2)
call("Runtime.evaluate", {
"expression": "document.title = 'CDP Demo'; document.body.innerHTML = '<h1>Hello CDP</h1>'",
})
time.sleep(0.2)
shot = call("Page.captureScreenshot", {"format": "png"})
with open("shot.png", "wb") as f:
f.write(base64.b64decode(shot["result"]["data"]))
print("截图已保存到 shot.png")
ws.close()
proc.terminate()
代码不长,关键在 call()。CDP 的消息是 JSON,发出去的时候带一个自增的 id、方法名和参数。浏览器返回时会把同样的 id 带回来,所以循环读消息、只处理 id 对得上的那条就行。中间可能夹杂事件消息,比如页面加载、控制台输出,这些没有 id,跳过。
Page.navigate 打开页面,Runtime.evaluate 执行一段 JS。这里没有真的去访问网站,而是把 about:blank 的标题和正文改掉,省得等网络。最后 Page.captureScreenshot 返回 base64 的 PNG,解码写文件。
几个常用的域
CDP 的命令按“域”分组,刚开始不用记太多,这三个够用一阵子:
Page:导航、加载事件、截图、打印 PDFRuntime:执行 JS、取返回值、监听 consoleNetwork:请求列表、响应头、拦截和修改请求
比如想抓某个接口的响应,先 Network.enable,再监听 Network.responseReceived 事件,拿到 requestId 后用 Network.getResponseBody 取内容。这个过程比 Selenium 直接,但事件处理要自己写,代码量也上去了。
值不值得用
如果你只是做常规的页面自动化,Playwright 更省心,选择器、等待、截图都封装好了。直接用 CDP 的优势在于控制细:性能指标、网络拦截、DOM 快照、跨页面会话,这些都能自己拼。调试自动化脚本时,也可以用它看浏览器到底收到了什么,比猜快。
我的建议是把它当成底层的调试工具,而不是日常首选。先跑通上面的例子,再挑一个你关心的域试两条命令,基本就有感觉了。
- 点赞
- 收藏
- 关注作者
评论(0)