一句话总结:写 CDP 自动化脚本时,80% 的时间花在”页面上现在有什么”这个问题上。一组趁手的调试脚本——查看页面文字、扫描可见按钮、检查弹窗、获取坐标——能把这 80% 的时间省下来。
目录
- 核心调试工具
- 页面信息快照
- 可见元素扫描
- 弹窗与遮罩层诊断
- 坐标定位器
- 综合诊断函数
- 总结
核心调试工具
页面状态快照
最常用的调试命令——同时获取 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) dump_page_info(ws) print() scan_buttons(ws) print() scan_inputs(ws) print() diagnose_dialogs(ws) print('=' * 50)
|
实践案例
在调试自动化脚本时按以下顺序排查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| page_snapshot(ws)
buttons = scan_buttons(ws)
dialogs = diagnose_dialogs(ws)
pos = locate_element(ws, '.target-btn')
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 自动化的第一原则。