如何在受限内存服务器上做浏览器自动化:CDP 复用单标签页实践
为什么不能每个任务都开一个新浏览器
在一台 2 核 1.7G 内存的入门云服务器上做浏览器自动化,如果沿用「每个脚本启动一次 Playwright/浏览器」的写法,很快就会遇到内存被吃满、进程被 OOM Killer 干掉的问题。一个完整的 Chromium 进程树常驻就要 500MB 以上,再叠加多个标签页,1.7G 的机器基本撑不住。
更省内存的思路是:只维持一个常驻的 headless Chrome 实例,通过 Chrome DevTools Protocol(CDP)直接复用它,并且全程只保留一个标签页。
第一步:常驻一个 Chrome,开启远程调试端口
用 systemd 用户服务把 Chrome 拉起来,固定 profile 目录,并暴露 CDP 端口:
google-chrome --headless=new --remote-debugging-port=9222 \
--user-data-dir=/path/to/profile
# 自检:应返回标签页 JSON 列表
curl -s http://127.0.0.1:9222/json/list
登录态、Cookie 都保存在 profile 里,服务重启后不用重新登录,这一点对需要长期保持登录的自动化非常关键。
第二步:用 WebSocket 直连 CDP,不启动新浏览器
关键是不要再调用 Playwright 的 launch——那会另起一个浏览器。正确做法是读取 http://127.0.0.1:9222/json/list,找到 type=page 的标签页,拿到它的 webSocketDebuggerUrl,用 websocket-client 建立连接后直接发送 Runtime.evaluate、Page.navigate 等命令:
from websocket import create_connection
import json, urllib.request
tabs = json.load(urllib.request.urlopen("http://127.0.0.1:9222/json/list"))
page = [t for t in tabs if t["type"] == "page"][0]
ws = create_connection(page["webSocketDebuggerUrl"], max_size=None)
ws.send(json.dumps({"id": 1, "method": "Runtime.evaluate",
"params": {"expression": "document.title", "returnByValue": True}}))
print(ws.recv())
第三步:一次只开一个标签页,用完立即关闭
很多操作(比如任务卡片上的「去完成」)会新开一个标签页。每处理完一项,就用 GET /json/close/<id> 把它关掉,让常驻只保留一个页面。这样可以避免标签页越积越多、内存缓慢上涨直至卡死。
# 关闭指定标签页
curl -s http://127.0.0.1:9222/json/close/<targetId>
# 数一下还剩几个 page
curl -s http://127.0.0.1:9222/json/list | grep -c '"type": "page"'
几点实战体会
- 点击要用真实鼠标事件。Vue/React 渲染的页面里,用 JS 的
element.click()常常被忽略;改用Input.dispatchMouseEvent按getBoundingClientRect()的中心坐标点击才可靠。 - 页面不可滚动时,先对目标元素
scrollIntoView({block:'center'}),再取坐标。 - 每次 IPC 调用都设超时,长等待放在脚本外层,避免连接冻结后大批命令超时。
- 定期
free -h和统计 Chrome 进程数,作为资源是否失控的体检指标。
小结
在受限内存的机器上,省内存的核心不是「更快的浏览器」,而是「更少的浏览器」。一个常驻 Chrome + CDP 直连 + 单标签页复用,配合用完即关的纪律,就能让浏览器自动化在小规格云服务器上稳定长跑。
- 点赞
- 收藏
- 关注作者
评论(0)