Playwright 登录态莫名失效?一次 Session Cookie 丢失的排查记录
现象:昨天还好好的,今天登录态没了
自动化脚本最让人头疼的一类问题:代码一行没改,昨天跑得好好的,今天突然登录失效了。
第一反应通常是"Cookie 过期了"。但打开开发者工具一看,登录态相关的 Cookie 明明都在:
cbc-sid .huaweicloud.com 到期时间 2027-10-31
vk .huaweicloud.com 到期时间 2027-10-31
ua .huaweicloud.com 到期时间 2027-10-31
有效期还有一年多,看起来完全正常。可页面就是显示未登录,访问需要登录的页面会被重定向到登录页。
这就是本文要拆解的问题:Cookie 在,为什么登录态还是丢了?
第一步:区分"持久 Cookie"和"Session Cookie"
这是整个排查的关键认知。浏览器里的 Cookie 分两类:
| 类型 | 特征 | 存储位置 |
|---|---|---|
| 持久 Cookie | 有明确的 Expires / Max-Age |
写入磁盘,浏览器重启后仍在 |
| Session Cookie | 无过期时间(expires = -1) |
通常只在内存中,浏览器关闭即失效 |
很多人(包括我)看到 cbc-sid 有效期到 2027 年,就以为登录态稳如泰山。但实际上,真正维持会话的往往是一批 Session Cookie。
用脚本把 Cookie 分类导出来看:
const cookies = await ctx.cookies();
cookies.forEach((c) => {
const flag = c.expires > 0
? 'PERSIST ' + new Date(c.expires * 1000).toISOString()
: 'SESSION (关闭即失效)';
console.log(`${flag} ${c.name} @ ${c.domain}`);
});
输出结果一目了然:
PERSIST 2027-10-31 cbc-sid @ .huaweicloud.com
PERSIST 2027-10-31 vk @ .huaweicloud.com
SESSION (关闭即失效) SSOJTC @ .auth.huaweicloud.com
SESSION (关闭即失效) CASTGC @ .id1.cloud.huawei.com
SESSION (关闭即失效) SessionID @ .huaweicloud.com
SESSION (关闭即失效) SSOTGC @ .auth.huaweicloud.com
cbc-sid 这类持久 Cookie 更像"设备标识",而真正的登录凭证是 SSOJTC / CASTGC / SessionID 这批 Session Cookie。
它们的有效期不是由 Expires 决定的,而是由服务端 session 的存活状态决定。客户端这边一旦丢失,就再也拿不回来了。
第二步:找到 Session Cookie 的丢失时机
既然是"关闭即失效",那问题就聚焦到:浏览器是怎么被关掉的?
翻回自己的自动化代码,答案很清楚了——为了图省事,清理进程时用的是强制杀:
# 危险操作
Get-CimInstance Win32_Process -Filter "Name='chrome.exe'" |
Where-Object { $_.CommandLine -like "*ms-playwright*" } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
Stop-Process -Force 相当于直接 kill -9,浏览器没有机会走正常的退出流程:
- 不会把内存中的 Session Cookie 刷写到磁盘
- 不会清理和保存会话状态
- 下次启动就是一个"干净"的浏览器
Playwright 自己的 ctx.close() 则不同,它会走完整的关闭流程,Session Cookie 有机会被保留下来(Chromium 支持"恢复上次会话"时会持久化这部分数据)。
所以:每强杀一次,就丢一次登录态。
第三步:三条对策
对策一:优雅关闭,别强杀
// 正确
await ctx.close();
// 或者封装一层,附加模式下不要关掉别人的浏览器
async function closeQuiet(ctx) {
if (!ctx || ctx.__attached) return; // 附加来的,不动它
try { await ctx.close(); } catch (e) {}
}
如果一定要杀进程,优先用 CloseMainWindow() 而不是 Stop-Process -Force,给它一个体面退出的机会。
对策二:CDP 附加复用,少起进程
既然启动/关闭是风险点,那就尽量少开关浏览器。Playwright 启动时带上调试端口:
const ctx = await chromium.launchPersistentContext(profileDir, {
args: ['--remote-debugging-port=9333', ...],
});
后续所有脚本优先尝试附加到已运行的实例:
async function open(opts) {
// 先探测有没有在跑的浏览器
try {
const browser = await chromium.connectOverCDP('http://127.0.0.1:9333', { timeout: 4000 });
const ctx = browser.contexts()[0];
if (ctx) { ctx.__attached = true; return ctx; } // 直接复用
} catch (e) { /* 没有,走正常启动 */ }
return await chromium.launchPersistentContext(profileDir, {...});
}
好处很直接:浏览器只启动一次,多个脚本轮流复用,Session Cookie 全程留在内存里,压根没有丢失的机会。
对策三:兜底——自动重新登录
前两条是"防",这条是"兜"。会话真失效了,脚本要能自己恢复,而不是直接报错退出:
// 用「必须登录才能访问」的页面做硬检测,比看 Cookie 可靠
async function checkLogin(ctx) {
const page = ctx.pages()[0];
await page.goto('https://example.com/dashboard');
const hasEditor = await page.evaluate(() => typeof window.appReady !== 'undefined');
const redirected = /\/login/i.test(page.url());
return { loggedIn: hasEditor && !redirected };
}
// 检测 → 失效则登录 → 再验证
let chk = await checkLogin(ctx);
if (!chk.loggedIn) {
await login(ctx); // 自动填账号密码、处理验证弹窗
await save(ctx, page); // 立刻固化会话
chk = await checkLogin(ctx);
}
这里有个容易踩的坑:不要用 Cookie 是否存在来判断登录态。
我们前面已经看到,cbc-sid 还在的时候页面照样是未登录的。用「访问需要登录的页面,看是否被重定向」才是可靠判据。
第四步:验证与固化
修好之后,验证一下三条链路是否都通:
// 1) 优雅关闭后重启,登录态是否还在?
// 2) 附加模式下,另一个脚本能否复用同一会话?
// 3) 手动清掉 Session Cookie 模拟失效,自动重登能否救回来?
const r = await login(ctx, { waitHumanMs: 900000 });
console.log('登录结果:', r); // { ok: true, url: '...' }
另外,每次运行结束都顺手把会话快照存一份:
await ctx.storageState({ path: './session/state.json' });
storageState 会把包括 Session Cookie 在内的所有 Cookie 序列化下来。虽然浏览器正常关闭时这些 Cookie 会丢,但有了这份快照,启动时可以主动回灌:
const st = JSON.parse(fs.readFileSync('./session/state.json', 'utf8'));
if (st.cookies?.length) await ctx.addCookies(st.cookies);
持久化目录 + 快照回灌 + 自动重登,三层叠加之后,登录态才算真正稳了。
小结
| 环节 | 做法 | 作用 |
|---|---|---|
| 关闭 | ctx.close() / closeQuiet() |
保住 Session Cookie |
| 运行 | CDP 附加复用 | 减少启停,会话常驻内存 |
| 检测 | 访问需登录页看重定向 | 比查 Cookie 可靠 |
| 兜底 | 自动重新登录 + 立刻固化 | 失效也能自愈 |
三条经验:
- "Cookie 还在"不等于"登录态还在" —— 持久 Cookie 只是设备标识,真正的凭证往往是 Session Cookie。
kill -9是自动化里的隐形杀手 —— 它绕过一切清理逻辑,包括你依赖的状态持久化。- 判据要选"行为"而不是"状态" —— 看页面是否被重定向,比看 Cookie 是否存在靠谱得多。
本文为实践记录,代码片段已做脱敏处理。不同站点的 Cookie 策略差异较大,具体以实际抓包结果为准。
- 点赞
- 收藏
- 关注作者
评论(0)