一句话总结:CDP 的 WebSocket 连接在长时间运行时可能因安全软件拦截、页面导航、浏览器资源紧张而断开。解决之道是建立”操作前心跳检测 → 断开自动重连 → 重试当前操作”的防御性编程模式。


目录

  1. 问题现象
  2. 断线原因分析
  3. 心跳检测 + 自动重连
  4. 页面导航后的连接恢复
  5. 完整封装
  6. 预防措施
  7. 总结

问题现象

在运行自动化脚本时,控制台突然输出:

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: # 超过 500MB,建议重启
print(f'警告:内存使用 {mem_mb:.0f}MB,建议重启浏览器')
return mem_mb

总结

问题 解决方案
安全软件断连 加入白名单 + 自动重连兜底
导航后断开 navigate_and_rebind 等待就绪
浏览器资源紧张 控制频率 + 内存监控
电源管理挂起 调整 Windows 电源设置

核心策略:不要试图完全避免断线(不可能),而是建立”操作前检查 → 断开自动重连 → 重试当前操作”的防御模式。配合安全软件白名单和合理的操作频率,可以将断线对自动化流程的影响降到最低。