跳到主要内容

🟠CDP Debugger 入门教程


import json
import websocket
import requests
import time

def get_websocket_url(port=9222):
"""获取 WebSocket 调试 URL"""
response = requests.get(f'http://localhost:{port}/json')
tabs = response.json()
return tabs[0]['webSocketDebuggerUrl']

class CDPDebugger:
"""CDP 调试器封装类"""

def __init__(self, ws_url):
self.ws = websocket.create_connection(ws_url)
self.command_id = 0

def send_command(self, method, params=None):
"""发送 CDP 命令"""
self.command_id += 1
command = {
"id": self.command_id,
"method": method,
"params": params or {}
}
print(f"\n>>> 发送命令: {method}")
print(f" 参数: {json.dumps(params or {}, indent=6)}")

self.ws.send(json.dumps(command))
response = json.loads(self.ws.recv())

print(f"<<< 响应: {json.dumps(response, indent=4, ensure_ascii=False)}")
return response

def wait_for_event(self, event_name, timeout=5):
"""等待特定事件"""
print(f"\n--- 等待事件: {event_name} ---")
start_time = time.time()

while time.time() - start_time < timeout:
try:
self.ws.settimeout(1)
message = self.ws.recv()
data = json.loads(message)

if 'method' in data:
print(f" 收到事件: {data['method']}")
if data['method'] == event_name:
print(f" 事件详情: {json.dumps(data['params'], indent=8, ensure_ascii=False)}")
return data
except:
continue

print(f" 超时: 未收到 {event_name} 事件")
return None

def close(self):
self.ws.close()


# ========================================
# 示例 1: 基础调试流程
# ========================================
def example_basic_debugging():
"""
演示基础调试命令组合:
1. Debugger.enable - 启用调试器
2. Runtime.enable - 启用运行时
3. Page.enable - 启用页面事件
4. Page.navigate - 导航到页面
"""
print("\n" + "="*60)
print("示例 1: 基础调试流程")
print("="*60)

debugger = CDPDebugger(get_websocket_url())

# 1. 启用调试器
debugger.send_command("Debugger.enable")

# 2. 启用运行时(用于执行 JavaScript)
debugger.send_command("Runtime.enable")

# 3. 启用页面事件
debugger.send_command("Page.enable")

# 4. 导航到页面
debugger.send_command("Page.navigate", {
"url": "https://example.com"
})

# 等待页面加载事件
debugger.wait_for_event("Page.loadEventFired")

debugger.close()


# ========================================
# 示例 2: 设置断点和暂停
# ========================================
def example_breakpoints():
"""
演示断点相关命令:
1. Debugger.setBreakpointByUrl - 按 URL 设置断点
2. Debugger.pause - 暂停执行
3. Debugger.resume - 恢复执行
4. Debugger.stepOver - 单步跳过
5. Debugger.stepInto - 单步进入
6. Debugger.stepOut - 单步跳出
"""
print("\n" + "="*60)
print("示例 2: 断点和单步调试")
print("="*60)

debugger = CDPDebugger(get_websocket_url())

debugger.send_command("Debugger.enable")
debugger.send_command("Runtime.enable")

# 1. 按 URL 设置断点
debugger.send_command("Debugger.setBreakpointByUrl", {
"lineNumber": 10,
"url": "https://example.com/script.js",
"columnNumber": 0,
"condition": "x > 5" # 条件断点
})

# 2. 暂停所有执行
debugger.send_command("Debugger.pause")

# 等待暂停事件
paused_event = debugger.wait_for_event("Debugger.paused")

if paused_event:
# 3. 单步跳过
debugger.send_command("Debugger.stepOver")

# 4. 单步进入函数
debugger.send_command("Debugger.stepInto")

# 5. 单步跳出函数
debugger.send_command("Debugger.stepOut")

# 6. 恢复执行
debugger.send_command("Debugger.resume")

debugger.close()


# ========================================
# 示例 3: 执行 JavaScript 和获取调用栈
# ========================================
def example_runtime_evaluation():
"""
演示运行时评估命令:
1. Runtime.evaluate - 执行 JavaScript
2. Debugger.evaluateOnCallFrame - 在调用帧上评估
3. Runtime.getProperties - 获取对象属性
"""
print("\n" + "="*60)
print("示例 3: JavaScript 执行和评估")
print("="*60)

debugger = CDPDebugger(get_websocket_url())

debugger.send_command("Debugger.enable")
debugger.send_command("Runtime.enable")

# 1. 在页面上下文中执行 JavaScript
result = debugger.send_command("Runtime.evaluate", {
"expression": "document.title",
"returnByValue": True
})

# 2. 执行更复杂的表达式
debugger.send_command("Runtime.evaluate", {
"expression": """
(function() {
return {
url: window.location.href,
userAgent: navigator.userAgent,
screenWidth: screen.width
};
})()
""",
"returnByValue": True
})

# 3. 获取全局对象
result = debugger.send_command("Runtime.evaluate", {
"expression": "window",
"returnByValue": False
})

if 'result' in result and 'result' in result['result']:
object_id = result['result']['result'].get('objectId')
if object_id:
# 4. 获取对象的属性
debugger.send_command("Runtime.getProperties", {
"objectId": object_id,
"ownProperties": True
})

debugger.close()


# ========================================
# 示例 4: 脚本管理
# ========================================
def example_script_management():
"""
演示脚本相关命令:
1. Debugger.getScriptSource - 获取脚本源代码
2. Debugger.setScriptSource - 热更新脚本(实验性)
3. Debugger.searchInContent - 在脚本中搜索
"""
print("\n" + "="*60)
print("示例 4: 脚本管理")
print("="*60)

debugger = CDPDebugger(get_websocket_url())

debugger.send_command("Debugger.enable")

# 等待脚本解析事件
script_event = debugger.wait_for_event("Debugger.scriptParsed", timeout=3)

if script_event and 'params' in script_event:
script_id = script_event['params'].get('scriptId')

if script_id:
# 1. 获取脚本源代码
debugger.send_command("Debugger.getScriptSource", {
"scriptId": script_id
})

# 2. 在脚本中搜索
debugger.send_command("Debugger.searchInContent", {
"scriptId": script_id,
"query": "function",
"caseSensitive": False,
"isRegex": False
})

debugger.close()


# ========================================
# 示例 5: 异常处理
# ========================================
def example_exception_handling():
"""
演示异常处理命令:
1. Debugger.setPauseOnExceptions - 设置异常时暂停
2. Debugger.setAsyncCallStackDepth - 设置异步调用栈深度
"""
print("\n" + "="*60)
print("示例 5: 异常处理")
print("="*60)

debugger = CDPDebugger(get_websocket_url())

debugger.send_command("Debugger.enable")
debugger.send_command("Runtime.enable")

# 1. 设置在所有异常时暂停
debugger.send_command("Debugger.setPauseOnExceptions", {
"state": "all" # 可选值: "none", "uncaught", "all"
})

# 2. 设置异步调用栈深度
debugger.send_command("Debugger.setAsyncCallStackDepth", {
"maxDepth": 32
})

# 3. 执行会抛出异常的代码
debugger.send_command("Runtime.evaluate", {
"expression": "throw new Error('测试异常')",
"returnByValue": True
})

# 等待异常暂停
debugger.wait_for_event("Debugger.paused")

debugger.close()


# ========================================
# 示例 6: 综合应用 - 性能分析
# ========================================
def example_performance_profiling():
"""
演示性能分析组合:
1. Profiler.enable - 启用性能分析器
2. Profiler.start - 开始记录
3. Profiler.stop - 停止记录
4. Coverage.enable - 启用代码覆盖率
"""
print("\n" + "="*60)
print("示例 6: 性能分析")
print("="*60)

debugger = CDPDebugger(get_websocket_url())

debugger.send_command("Debugger.enable")
debugger.send_command("Profiler.enable")

# 1. 开始性能分析
debugger.send_command("Profiler.start")

# 2. 执行一些代码
debugger.send_command("Runtime.evaluate", {
"expression": """
for(let i = 0; i < 1000000; i++) {
Math.sqrt(i);
}
"""
})

# 3. 停止性能分析并获取结果
profile = debugger.send_command("Profiler.stop")

# 4. 启用代码覆盖率
debugger.send_command("Profiler.startPreciseCoverage", {
"callCount": True,
"detailed": True
})

time.sleep(2)

# 5. 获取覆盖率数据
debugger.send_command("Profiler.takePreciseCoverage")

debugger.close()


# ========================================
# 主函数 - 运行所有示例
# ========================================
if __name__ == "__main__":
print("\n" + "="*60)
print("CDP Debugger 常用命令示例集")
print("="*60)
print("\n请确保 Chrome 已使用以下命令启动:")
print("chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug")
print("\n")

try:
# 运行所有示例
example_basic_debugging()
time.sleep(1)

example_breakpoints()
time.sleep(1)

example_runtime_evaluation()
time.sleep(1)

example_script_management()
time.sleep(1)

example_exception_handling()
time.sleep(1)

example_performance_profiling()

print("\n" + "="*60)
print("所有示例执行完成!")
print("="*60)

except requests.exceptions.ConnectionError:
print("\n❌ 错误: 无法连接到 Chrome")
print("请确保 Chrome 已使用 --remote-debugging-port=9222 启动")
except Exception as e:
print(f"\n❌ 发生错误: {e}")
import traceback
traceback.print_exc()