Bootstrap

Chrome DevTools Protocol 入门:相关概念

前言

在上篇文章中,我们快速地使用了 Chrome DevTools Protocol(CDP)发送指令,成功实现了一些浏览器自动化操作。然而,要想深入使用 CDP,还需要对其中的 协议概念 有更深入的理解。这些概念不仅是我们与浏览器交互的基础,还决定了我们如何构建更复杂、更灵活的调试和自动化操作。为了让这些概念更容易理解,我们将在这里通过实例化的方式,打一些比方,让抽象的概念变得更加具象化。


1. 域(Domain) —— 功能模块的“房间”

想象一下,Chrome DevTools Protocol 是一栋大房子,而这栋房子有很多房间,每个房间都有不同的功能区域。每个房间就是一个 域(Domain)。比如:

  • Page 就像一个专门用于管理页面导航、截图、事件的房间。
  • Network 房间负责监控和处理所有的网络请求,像是管家在查看有哪些资源进出。
  • DOM 房间则是维护和操作网页的 DOM 树结构,类似一个园丁在修剪和修改网页中的元素。

这些“房间”之间是相互独立的,你需要去到不同的房间(域)才能执行特定的操作。比如你要修改页面的样式,就得进入 CSS 这个房间;而如果你想调试 JavaScript,则需要进入 Debugger 房间。

每个房间中有不同的 方法事件,这正是你可以利用的“工具”和“提醒”。


2. 方法(Method) —— 房间中的“操作工具”

在每个“房间”中,我们有各种工具来完成不同的操作。这些工具就是 CDP 中的 方法(Method)。可以把方法想象成每个房间里的“按钮”,按下不同的按钮,就会触发不同的行为:

  • Page 房间中,按下 Page.navigate 按钮,就会让页面导航到指定的 URL,好像打开了一扇新的门,进入了另一个房间。
  • Runtime 房间中,按下 Runtime.evaluate 按钮,就像在房间里执行一段代码,告诉房间该做什么事情。

每个方法通常需要一些 输入参数,这些参数就像是操作工具时所需的说明书。如果你要导航页面,你得告诉 Page.navigate 按钮:“我要去的目标 URL 是什么”。同样,工具执行后通常也会给你一个 返回值,告诉你执行的结果如何,比如页面是否成功导航。

类比:

想象你进入一个 Page 房间,看到墙上有个大按钮,上面写着“导航”。你输入目标地址按下按钮(调用方法时传递参数),浏览器就会载入该页面,并告诉你“页面已经打开”(返回结果)。


3. 事件(Event) —— 自动弹出的“提醒信号”

在每个房间里,不仅有工具可以操作,有时候你会收到一些 提醒信号(事件),告诉你房间里发生了什么变化。这些 事件(Event) 是浏览器主动发出的消息,类似房间中突然响起的警报,告诉你某件事情发生了。

  • 比如在 Page 房间中,当页面加载完成时,系统会自动发出 Page.loadEventFired 事件,就像一个完成通知,提醒你“页面加载已经完成”。
  • Network 房间中,当网络请求发出时,浏览器会发出 Network.requestWillBeSent 事件,提醒你“有一个网络请求刚刚发送”。

事件是被动的,你并不需要主动按下什么按钮或发出请求来获取它们。你只需要订阅这些事件,当事件触发时,浏览器会自动通知你。你就像在房间里安装了监控设备,房间发生了某些事情,它会立刻通知你。

类比:

想象你正在 Network 房间里走动,突然一盏红灯闪烁起来,显示“有新的请求正在发送”(Network.requestWillBeSent 事件)。你可以根据这个提示决定接下来要做的操作,比如拦截或查看这个请求。


4. 类型(Type) —— 方法和事件的“数据结构”

无论是方法的输入参数还是事件的输出结果,所有的数据都需要以某种结构表示出来。这些数据结构就是 类型(Type)。可以把 类型 看作是传递消息时使用的“容器”或“数据模型”。

  • 比如在 DOM 房间里,你可以拿到一个 DOM.Node 类型的对象,这个对象就像一个树形图,里面包含了节点的详细信息,如节点的 ID、名称和子节点数量。
  • Network 房间里,事件返回的 Network.Request 对象包含请求的详细信息,比如 URL、请求头等。

类型 保证了数据的结构化,让浏览器和客户端之间的数据交互更加清晰和有序。你可以理解为,房间里的“工具”所接收的指令(参数)和它们返回的结果都装在特定形状的盒子(类型)里,盒子的结构要符合工具的预期。

类比:

比如,你需要给 Runtime.evaluate 方法传递一段 JavaScript 代码,代码这个数据需要装在一个特定的“盒子”里,这个盒子就是 expression 类型的参数。返回结果时,浏览器会给你另一个特定的“盒子”,里面是执行后的结果数据,比如它的值和类型。


实例:通过 CDP 控制页面的导航与监控

下面是一个使用 Pythonwebsockets 库的示例,展示如何通过 Chrome DevTools Protocol (CDP) 使用 WebSocket 连接控制页面导航并监控事件。我们将实现以下功能:

  1. 通过 WebSocket 连接到 Chrome DevTools Protocol。
  2. 使用 Page.navigate 指令导航到指定页面。
  3. 订阅 Page.loadEventFired 事件,监控页面加载完成。
  4. 订阅 Network.requestWillBeSent 事件,监控页面发出的网络请求。

环境准备

首先,确保你已经安装了 websockets 库:

pip install websockets

示例代码

import asyncio
import websockets
import json

# Chrome DevTools Protocol WebSocket URL (replace with actual URL from http://localhost:9222/json/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq)
ws_url = 'ws://localhost:9222/devtools/page/{your-page-id}'

# Message ID counter
message_id = 0

# Function to send messages over WebSocket
async def send_message(ws, method, params=None):
    global message_id
    message_id += 1
    message = {
        'id': message_id,
        'method': method,
    }
    if params:
        message['params'] = params
    await ws.send(json.dumps(message))
    print(f"Sent: {message}")

# Function to navigate to a page
async def navigate_to_page(ws, url):
    await send_message(ws, 'Page.navigate', {'url': url})

# Function to enable domains like Page and Network
async def enable_domains(ws):
    await send_message(ws, 'Page.enable')
    await send_message(ws, 'Network.enable')

# Function to handle received messages
async def handle_message(message):
    data = json.loads(message)

    # Page load event fired
    if data.get('method') == 'Page.loadEventFired':
        print("Page has finished loading.")

    # Network request sent
    if data.get('method') == 'Network.requestWillBeSent':
        request = data['params']['request']
        print(f"Network request sent: {request['url']}")

# Main function to run WebSocket client
async def run():
    async with websockets.connect(ws_url) as ws:
        print("Connected to Chrome DevTools Protocol")

        # Enable Page and Network domains
        await enable_domains(ws)

        # Navigate to a test page
        await navigate_to_page(ws, 'https://example.com')

        # Listen for events and responses
        while True:
            message = await ws.recv()
            await handle_message(message)

# Run the WebSocket client
asyncio.get_event_loop().run_until_complete(run())

代码解释

  1. ws_url: 你需要从 Chrome 获取实际的 webSocketDebuggerUrl,通过访问 http://localhost:9222/json/version 获取。
  2. send_message: 这个函数负责通过 WebSocket 向 Chrome 发送 CDP 指令。每条指令都有唯一的 idmethod,有些指令还需要 params
  3. navigate_to_page: 调用 Page.navigate 指令,将浏览器导航到指定 URL。
  4. enable_domains: 启用 PageNetwork 域,以便接收相关的事件。
  5. handle_message: 根据接收到的事件进行处理。当 Page.loadEventFired 触发时,表示页面加载完成。当 Network.requestWillBeSent 触发时,捕获并打印出网络请求信息。
  6. run: 主函数,首先通过 WebSocket 连接到 Chrome,然后发送指令并接收事件。

运行步骤

  1. 打开 Chrome 并启用远程调试,使用命令行启动 Chrome:

bash复制代码chrome.exe --remote-debugging-port=9222

  1. 访问 http://localhost:9222/json/version,找到对应的 webSocketDebuggerUrl,并替换代码中的 {your-page-id}
  2. 运行该 Python 脚本:

bash复制代码python your_script.py

结果

  • 脚本将打开一个新页面导航到 https://example.com
  • 当页面加载完成时,脚本会输出 "Page has finished loading"
  • 每当页面发出网络请求时,脚本会输出请求的 URL。

这个示例演示了如何通过 CDP 使用 Python 和 WebSocket 实现页面控制和事件监控。在实际项目中,你可以扩展代码来处理更多的指令和事件。


总结

通过这些实例化的比喻和解释,我们可以更直观地理解 Chrome DevTools Protocol 中的 方法事件类型。它们就像一个复杂的建筑体系,每个房间(域)负责不同的功能,每个按钮(方法)执行特定的操作,事件是自动发出的提醒信号,而数据则装在特定的盒子里(类型)进行传递。理解这些概念后,我们可以更加灵活高效地使用 CDP 来完成调试、自动化任务。

;