Puppeteer 如何管理 Cookie 与存储实现会话持久化?
核心回答
Puppeteer 通过 page.cookies() / page.setCookie() 管理 Cookie,通过 page.evaluate() 操作 LocalStorage、SessionStorage 和 IndexedDB,结合 userDataDir 或手动序列化实现会话持久化,利用 browser.createIncognitoBrowserContext() 实现多账户隔离。
三种会话持久化方案的对比:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| userDataDir | 长期保持登录态 | 最简单,自动持久化所有数据 | 数据量大,不易清理 |
| 手动序列化 Cookie + Storage | 精确控制需要持久化的数据 | 灵活可控,文件小 | 需要手动处理每种存储 |
| Incognito Context + 手动保存 | 多账户并行 | 完全隔离,互不干扰 | 上下文关闭后数据丢失 |
Cookie 管理
读取与设置 Cookie
Puppeteer 提供了简洁的 Cookie API:
javascript// 获取当前页面所有 Cookie const cookies = await page.cookies(); // 获取指定 URL 的 Cookie(可跨域获取第三方 Cookie) const cookies = await page.cookies('https://api.example.com'); // 设置单个 Cookie await page.setCookie({ name: 'session_id', value: 'abc123', domain: '.example.com', path: '/', expires: Math.floor(Date.now() / 1000) + 3600, httpOnly: true, secure: true, sameSite: 'Lax' }); // 批量设置 await page.setCookie( { name: 'token', value: 'xxx', domain: '.example.com' }, { name: 'lang', value: 'zh', domain: '.example.com' } );
删除与清除 Cookie
javascript// 删除指定 Cookie(需匹配 name 和 domain) await page.deleteCookie({ name: 'session_id', domain: '.example.com' }); // 清除所有 Cookie const allCookies = await page.cookies(); await page.deleteCookie(...allCookies);
Cookie 的 SameSite 策略
Chrome 80+ 默认将未声明 SameSite 的 Cookie 视为 Lax,这对跨域场景影响显著:
- Strict:仅同站请求携带,最安全但体验差(从外部链接跳入不带 Cookie)
- Lax:同站请求 + 顶级导航的 GET 请求携带(默认值)
- None:跨站也携带,但必须同时设置
secure: true
在 Puppeteer 中模拟跨站场景时,如果目标站点依赖第三方 Cookie,需要显式设置 sameSite: 'None' 并确保 secure: true,否则请求可能不带 Cookie 导致鉴权失败。
浏览器存储管理
LocalStorage
LocalStorage 以键值对形式持久化数据,同源共享,无过期时间:
javascript// 读取全部 const lsData = await page.evaluate(() => { return Object.fromEntries( Array.from({ length: localStorage.length }, (_, i) => { const key = localStorage.key(i); return [key, localStorage.getItem(key)]; }) ); }); // 写入 await page.evaluate(() => { localStorage.setItem('user_id', '12345'); localStorage.setItem('prefs', JSON.stringify({ theme: 'dark' })); }); // 删除指定项 / 清空 await page.evaluate(() => localStorage.removeItem('user_id')); await page.evaluate(() => localStorage.clear());
SessionStorage
SessionStorage 与 LocalStorage API 相同,但数据仅在当前标签页生命周期内有效,关闭标签页即清除:
javascriptawait page.evaluate(() => { sessionStorage.setItem('temp_key', 'temp_value'); }); const data = await page.evaluate(() => { return Object.fromEntries( Array.from({ length: sessionStorage.length }, (_, i) => { const key = sessionStorage.key(i); return [key, sessionStorage.getItem(key)]; }) ); });
IndexedDB
IndexedDB 适合存储结构化数据,操作较为复杂,Puppeteer 中需要通过 page.evaluate 异步操作:
javascriptconst dbData = await page.evaluate(async () => { return new Promise((resolve, reject) => { const req = indexedDB.open('myDB', 1); req.onsuccess = (e) => { const db = e.target.result; const tx = db.transaction(['store1'], 'readonly'); const store = tx.objectStore('store1'); const getAll = store.getAll(); getAll.onsuccess = () => resolve(getAll.result); getAll.onerror = () => reject(getAll.error); }; req.onerror = () => reject(req.error); }); });
会话持久化
方案一:userDataDir(推荐用于长期持久化)
启动浏览器时指定 userDataDir,Chrome 会将 Cookie、LocalStorage、SessionStorage、IndexedDB 等所有用户数据保存到该目录,下次启动自动恢复:
javascriptconst browser = await puppeteer.launch({ userDataDir: './user_data/session1' // 指定持久化目录 }); const page = await browser.newPage(); await page.goto('https://example.com'); // 所有登录状态、Cookie、Storage 自动持久化到磁盘
这种方式最简单,但要注意:目录会随使用逐渐增大,长期运行需要定期清理。
方案二:手动序列化 Cookie + Storage
当只需要保存部分数据时,手动序列化更精确:
javascriptasync function saveSession(page, filePath) { const fs = require('fs'); const cookies = await page.cookies(); const localStorage = await page.evaluate(() => { return Object.fromEntries( Array.from({ length: localStorage.length }, (_, i) => { const key = localStorage.key(i); return [key, localStorage.getItem(key)]; }) ); }); fs.writeFileSync(filePath, JSON.stringify({ cookies, localStorage })); } async function restoreSession(page, filePath) { const fs = require('fs'); const { cookies, localStorage } = JSON.parse(fs.readFileSync(filePath, 'utf8')); // 先设置 Cookie,再导航(确保域名匹配) await page.setCookie(...cookies); await page.evaluate((data) => { for (const [k, v] of Object.entries(data)) { localStorage.setItem(k, v); } }, localStorage); }
恢复顺序很重要:先 setCookie 再导航到目标页面,这样页面加载时就能携带正确的 Cookie。
多账户管理
使用 Incognito Browser Context
每个 Incognito 上下文拥有独立的 Cookie 和 Storage,互不干扰:
javascriptconst browser = await puppeteer.launch(); // 账户 A const ctxA = await browser.createIncognitoBrowserContext(); const pageA = await ctxA.newPage(); await pageA.goto('https://example.com/login'); // ... 登录账户 A // 账户 B(完全隔离) const ctxB = await browser.createIncognitoBrowserContext(); const pageB = await ctxB.newPage(); await pageB.goto('https://example.com/login'); // ... 登录账户 B // 操作完成后关闭上下文 await ctxA.close(); await ctxB.close();
多账户持久化方案
如果需要在不同运行间恢复多个账户的会话,可以结合 userDataDir 和手动序列化:
javascriptasync function loginAndSave(account, sessionDir) { const browser = await puppeteer.launch({ userDataDir: sessionDir // 每个账户独立目录 }); const page = await browser.newPage(); await page.goto('https://example.com/login'); await page.type('#username', account.username); await page.type('#password', account.password); await page.click('#login-button'); await page.waitForNavigation(); await browser.close(); // 数据自动保存到 sessionDir }
安全注意事项
- 敏感数据保护:不要在代码中硬编码密码,使用环境变量
process.env.PASSWORD;将包含会话信息的文件加入.gitignore - Cookie 安全属性:设置
httpOnly: true防 XSS、secure: true限 HTTPS 传输、sameSite: 'Strict'防 CSRF - 会话过期处理:检查 Cookie 的
expires字段,过期后重新登录,避免用失效会话发请求 - 第三方 Cookie 限制:Chrome 逐步限制第三方 Cookie,跨域场景需使用
sameSite: 'None'并配合secure: true
追问
Q: userDataDir 和手动序列化如何选择?
简单场景(只需保持登录)用 userDataDir,复杂场景(需要跨环境迁移、选择性恢复)用手动序列化。手动序列化的优势在于文件小、可审计、可跨机器使用;userDataDir 的优势是零配置、自动覆盖所有存储类型。
Q: 如何检测 Cookie 是否生效?
设置 Cookie 后,通过 page.cookies(url) 验证返回的 Cookie 列表中是否包含目标项,或在导航后检查页面行为(如是否仍处于登录态)。注意 Cookie 的 domain 和 path 必须匹配目标 URL,否则不会被发送。
Q: Incognito Context 和 CDP Session 有什么区别?
Incognito Context 是浏览器层面的隔离,拥有独立的 Cookie 和 Storage;CDP Session 是 DevTools 协议层面的隔离,允许多个客户端独立与页面交互但不隔离存储数据。多账户场景应使用 Incognito Context。