一句话总结:CDP 的 WebSocket 连接在长时间运行时可能因安全软件拦截、页面导航、浏览器资源紧张而断开。解决之道是建立”操作前心跳检测 → 断开自动重连 → 重试当前操作”的防御性编程模式。
目录
- 问题现象
- 断线原因分析
- 心跳检测 + 自动重连
- 页面导航后的连接恢复
- 完整封装
- 预防措施
- 总结
问题现象
在运行自动化脚本时,控制台突然输出:
1
| ConnectionAbortedError: [WinError 10053] 主机中的软件中止了一个已建立的连接
|
此时脚本崩溃,之前的进度全部丢失。需要从头开始运行。
这个问题在长时间运行的脚本中(如批量上传 100 章内容,总耗时 30+ 分钟)尤其常见。
断线原因分析
根据实际排查,常见原因按概率排序:
1. 安全软件拦截(最高概率)
360、腾讯管家等安全软件将本地 WebSocket 连接误判为威胁,主动切断连接。
特征:运行一段时间后随机断开,没有固定规律。
验证方法:检查安全软件的日志,看是否有”拦截网络攻击”之类的记录。
2. 页面导航导致 target 短暂不可用
当 CDP 发 Page.navigate 导航到新页面时,Chrome 会短暂断开旧的 WebSocket 连接然后重建。
特征:断线总发生在 Page.navigate 调用或页面跳转之后。
3. 浏览器资源紧张
浏览器长时间运行后内存占用上升,Chrome 可能主动回收资源。
特征:运行多小时后出现,伴随浏览器变慢。
4. Windows 电源管理
Windows 将浏览器进程挂起或降级优先级。
特征:电脑锁屏或屏幕关闭后出现。
心跳检测 + 自动重连
基础方案:操作前检查
1 2 3 4 5 6 7 8 9 10
| def ensure_ws_alive(ws): """检查 WebSocket 是否存活""" try: cmd(ws, 'Runtime.evaluate', { 'expression': '1+1', 'returnByValue': True }) return True except: return False
|
重连函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def reconnect_ws(): """重新建立 CDP WebSocket 连接""" for attempt in range(3): try: ws_url = get_page_ws() ws = websocket.create_connection(ws_url, timeout=30) cmd(ws, 'Page.enable') cmd(ws, 'Runtime.enable') return ws except Exception as e: if attempt < 2: time.sleep(2) else: raise return None
|
防御性执行模式
核心模式是:每次操作前检查 → 断开就重连 → 重试操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def safe_execute(ws, func, *args, max_retries=2, **kwargs): """安全执行函数:自动重连后重试""" for attempt in range(max_retries): try: if not ensure_ws_alive(ws): ws = reconnect_ws() if ws is None: raise Exception('重连失败') return func(ws, *args, **kwargs) except (websocket.WebSocketConnectionClosedException, ConnectionAbortedError, ConnectionResetError) as e: if attempt < max_retries - 1: print(f'连接断开(#{attempt+1}),正在重连...') ws = reconnect_ws() time.sleep(1) else: raise
|
使用示例
1 2 3 4 5 6 7 8 9 10 11 12
| def process_chapter(ws, chapter_data): """处理单个章节(会被 safe_execute 包装)""" return True
ws = initial_ws for chapter in chapters: result = safe_execute(ws, process_chapter, chapter) if not result: print(f'章节 {chapter.id} 处理失败') break
|
页面导航后的连接恢复
导航到新页面后,CDP 的 WebSocket 连接可能存活,但页面的执行上下文已经变了。需要重新获取 target:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def navigate_and_rebind(ws, url): """导航到新页面并等待就绪""" cmd(ws, 'Page.navigate', {'url': url}) t0 = time.time() while time.time() - t0 < 15: try: state = js(ws, 'document.readyState') if state == 'complete': return True except: ws = reconnect_ws() time.sleep(0.5) return False
|
完整封装
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| class CDPConnection: """带自动重连的 CDP 连接管理器""" def __init__(self, port=9222): self.port = port self.ws = None self.connect() def connect(self): """建立初始连接""" ws_url = get_page_ws() self.ws = websocket.create_connection(ws_url, timeout=30) cmd(self.ws, 'Page.enable') cmd(self.ws, 'Runtime.enable') print('CDP 连接已建立') def reconnect(self): """断线重连""" print('正在重连 CDP...') try: self.ws.close() except: pass time.sleep(1) self.connect() def is_alive(self): """检查连接是否存活""" try: cmd(self.ws, 'Runtime.evaluate', { 'expression': '1+1', 'returnByValue': True }) return True except: return False def execute(self, func, *args, max_retries=2, **kwargs): """安全执行操作""" for attempt in range(max_retries): try: if not self.is_alive(): self.reconnect() return func(self.ws, *args, **kwargs) except (websocket.WebSocketConnectionClosedException, ConnectionAbortedError, ConnectionResetError) as e: if attempt < max_retries - 1: self.reconnect() else: raise def close(self): """关闭连接""" try: self.ws.close() except: pass
conn = CDPConnection() for ch in chapters: conn.execute(process_chapter, ch) conn.close()
|
预防措施
除了被动重连,也可以主动降低断线概率:
1. 安全软件白名单
在安全软件中将以下程序加入白名单:
python.exe(你的脚本)
chrome.exe(浏览器进程)
2. 减少不必要的导航
1 2 3 4 5 6 7
| for url in urls: cmd(ws, 'Page.navigate', {'url': url})
for url in urls: result = cmd(ws, 'Target.createTarget', {'url': url})
|
3. 控制操作频率
1 2 3 4 5 6 7 8
| for item in items: process(item)
for item in items: process(item) time.sleep(0.5)
|
4. 监控内存
1 2 3 4 5 6 7 8 9 10
| def check_memory(ws): """检查页面内存使用""" result = cmd(ws, 'Runtime.evaluate', { 'expression': 'performance.memory.usedJSHeapSize', 'returnByValue': True }) mem_mb = result['result']['value'] / 1024 / 1024 if mem_mb > 500: print(f'警告:内存使用 {mem_mb:.0f}MB,建议重启浏览器') return mem_mb
|
总结
| 问题 |
解决方案 |
| 安全软件断连 |
加入白名单 + 自动重连兜底 |
| 导航后断开 |
navigate_and_rebind 等待就绪 |
| 浏览器资源紧张 |
控制频率 + 内存监控 |
| 电源管理挂起 |
调整 Windows 电源设置 |
核心策略:不要试图完全避免断线(不可能),而是建立”操作前检查 → 断开自动重连 → 重试当前操作”的防御模式。配合安全软件白名单和合理的操作频率,可以将断线对自动化流程的影响降到最低。