一句话总结:写 CDP 自动化脚本时,80% 的时间花在”页面上现在有什么”这个问题上。一组趁手的调试脚本——查看页面文字、扫描可见按钮、检查弹窗、获取坐标——能把这 80% 的时间省下来。


目录

  1. 核心调试工具
  2. 页面信息快照
  3. 可见元素扫描
  4. 弹窗与遮罩层诊断
  5. 坐标定位器
  6. 综合诊断函数
  7. 总结

核心调试工具

页面状态快照

最常用的调试命令——同时获取 URL、标题、页面文字:

1
2
3
4
5
6
7
8
9
10
11
12
def page_snapshot(ws):
"""获取页面当前状态的快照"""
url = js(ws, 'window.location.href')
title = js(ws, 'document.title')
text = js(ws, 'document.body.innerText.substring(0, 1000)')

print('=' * 40)
print(f'URL: {url}')
print(f'Title: {title}')
print(f'Text: {text[:300]}...')
print('=' * 40)
return {'url': url, 'title': title, 'text': text}

用途:确认页面是否正确加载、登录状态、是否有异常提示。

页面就绪检测

time.sleep() 更可靠的等待方式:

1
2
3
4
5
6
7
8
9
10
def wait_page_ready(ws, timeout=15):
"""等待页面加载完成(document.readyState === 'complete')"""
t0 = time.time()
while time.time() - t0 < timeout:
state = js(ws, 'document.readyState')
if state == 'complete':
return True
time.sleep(0.5)
print(f'⚠️ 页面加载超时({timeout}秒)')
return False

页面信息快照

调试信息转储

一次性输出页面上的所有调试信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def dump_page_info(ws):
"""输出页面调试信息"""
info = {
'url': js(ws, 'window.location.href'),
'title': js(ws, 'document.title'),
'readyState': js(ws, 'document.readyState'),
'viewport': js(ws, 'JSON.stringify({w: window.innerWidth, h: window.innerHeight})'),
'scrollPos': js(ws, 'JSON.stringify({x: window.scrollX, y: window.scrollY})'),
'totalHeight': js(ws, 'document.documentElement.scrollHeight'),
}

print('── 页面信息 ──')
for k, v in info.items():
print(f' {k}: {v}')

return info

可见元素扫描

所有可见按钮

自动扫描页面上所有可见的按钮,显示文本和位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def scan_buttons(ws):
"""扫描页面上所有可见按钮"""
buttons = js(ws, """
(() => {
const r = [];
const btns = document.querySelectorAll('button, a, [role="button"]');
for (const b of btns) {
if (b.offsetParent === null) continue;
const rect = b.getBoundingClientRect();
const txt = (b.innerText || '').trim().substring(0, 40);
const aria = (b.getAttribute('aria-label') || '').substring(0, 40);
if (txt || aria) {
r.push({
text: txt,
aria: aria,
disabled: b.disabled || b.classList.contains('disabled'),
x: Math.round(rect.left),
y: Math.round(rect.top),
w: Math.round(rect.width),
h: Math.round(rect.height)
});
}
}
return JSON.stringify(r);
})()
""")

buttons = json.loads(buttons) if buttons else []
print(f'── 可见按钮({len(buttons)} 个)──')
for b in buttons:
flag = ' ⛔' if b['disabled'] else ''
print(f' "{b["text"]}" [{b["x"]},{b["y"]} {b["w"]}x{b["h"]}]{flag}')

return buttons

所有可见输入框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def scan_inputs(ws):
"""扫描页面上所有可见输入框"""
inputs = js(ws, """
(() => {
const r = [];
const selectors = 'input:not([type=hidden]), textarea, [contenteditable="true"]';
const els = document.querySelectorAll(selectors);
for (const el of els) {
if (el.offsetParent === null) continue;
const rect = el.getBoundingClientRect();
const placeholder = el.getAttribute('placeholder') || '';
const name = el.getAttribute('name') || '';
const val = (el.value || el.innerText || '').substring(0, 30);
r.push({
tag: el.tagName,
type: el.getAttribute('type') || '',
name: name,
placeholder: placeholder,
value: val,
x: Math.round(rect.left),
y: Math.round(rect.top)
});
}
return JSON.stringify(r);
})()
""")

inputs = json.loads(inputs) if inputs else []
print(f'── 可见输入框({len(inputs)} 个)──')
for inp in inputs:
desc = inp['placeholder'] or inp['name'] or f'<{inp["tag"]} type={inp["type"]}>'
print(f' {desc} = "{inp["value"]}" at [{inp["x"]},{inp["y"]}]')

return inputs

弹窗与遮罩层诊断

弹窗诊断

当自动化卡在弹窗上时,诊断弹窗的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def diagnose_dialogs(ws):
"""诊断当前页面的弹窗状态"""
info = js(ws, """
(() => {
const dialogs = document.querySelectorAll(
'[class*="dialog"],[class*="modal"],[class*="overlay"],' +
'[class*="mask"],[role="dialog"]'
);
return JSON.stringify(Array.from(dialogs).map(d => ({
class: d.className.substring(0, 60),
visible: d.offsetParent !== null,
zIndex: window.getComputedStyle(d).zIndex,
opacity: window.getComputedStyle(d).opacity,
text: (d.innerText || '').trim().substring(0, 200)
})));
})()
""")

dialogs = json.loads(info) if info else []
print(f'── 弹窗诊断({len(dialogs)} 个)──')
for d in dialogs:
vis = '👁' if d['visible'] else '👻'
print(f' {vis} z={d["zIndex"]} op={d["opacity"]} class={d["class"][:40]}')
if d['visible'] and d['text']:
print(f' text: {d["text"][:100]}')

return dialogs

坐标定位器

获取元素坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def locate_element(ws, css_selector):
"""获取元素的位置信息"""
info = js(ws, f"""
(() => {{
const el = document.querySelector('{css_selector}');
if (!el) return null;
const r = el.getBoundingClientRect();
return JSON.stringify({{
tag: el.tagName,
text: (el.innerText || '').trim().substring(0, 50),
visible: el.offsetParent !== null,
x: Math.round(r.left),
y: Math.round(r.top),
w: Math.round(r.width),
h: Math.round(r.height),
centerX: Math.round(r.left + r.width / 2),
centerY: Math.round(r.top + r.height / 2)
}});
}})()
""")

if info:
info = json.loads(info)
print(f'── 元素定位 ──')
for k, v in info.items():
print(f' {k}: {v}')
else:
print('❌ 未找到元素')

return info

检查元素是否存在并可交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def element_status(ws, css_selector):
"""全面检查元素的状态"""
return js(ws, f"""
(() => {{
const el = document.querySelector('{css_selector}');
if (!el) return 'NOT_FOUND';
return JSON.stringify({{
exists: true,
visible: el.offsetParent !== null,
disabled: el.disabled === true,
readonly: el.readOnly === true,
displayed: window.getComputedStyle(el).display !== 'none',
opacity: window.getComputedStyle(el).opacity,
pointerEvents: window.getComputedStyle(el).pointerEvents,
zIndex: window.getComputedStyle(el).zIndex,
rect: el.getBoundingClientRect()
}});
}})()
""")

用途:确认按钮是否被禁用、被遮罩层遮挡、或被 CSS 隐藏。

综合诊断函数

一个函数输出所有调试信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def full_diagnosis(ws):
"""综合诊断:输出所有页面状态信息"""
print('\\n' + '=' * 50)
print('CDP 页面综合诊断')
print('=' * 50)

# 1. 基本信息
dump_page_info(ws)
print()

# 2. 可见按钮
scan_buttons(ws)
print()

# 3. 可见输入框
scan_inputs(ws)
print()

# 4. 弹窗诊断
diagnose_dialogs(ws)
print('=' * 50)

实践案例

在调试自动化脚本时按以下顺序排查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 先看页面状态
page_snapshot(ws)

# 2. 确认要操作的按钮是否存在
buttons = scan_buttons(ws)
# 如果找不到目标按钮 → 检查弹窗是否遮挡

# 3. 检查弹窗
dialogs = diagnose_dialogs(ws)
# 如果有弹窗遮挡 → 先处理弹窗

# 4. 获取目标元素坐标
pos = locate_element(ws, '.target-btn')
# 用坐标做 CDP 鼠标点击
cdp_click(ws, pos['centerX'], pos['centerY'])

总结

脚本 用途 最常用在
page_snapshot() 确认页面状态 导航后第一件事
wait_page_ready() 等待页面加载 代替 time.sleep()
scan_buttons() 找可以点击的按钮 找不到目标时
scan_inputs() 找输入框 填表操作前
diagnose_dialogs() 检查弹窗遮挡 点击无效时
locate_element() 获取点击坐标 CDP 鼠标点击前
element_status() 检查元素可交互性 调试点击为什么无效
full_diagnosis() 一键全面诊断 脚本卡住时

总结:调试脚本是 CDP 自动化开发的”瑞士军刀”。与其反复猜测页面状态,不如一个 full_diagnosis() 输出所有信息。先检查再操作——这应该成为 CDP 自动化的第一原则。