5月28日 07:15

Puppeteer 如何管理 Cookie 与存储实现会话持久化?

核心回答

Puppeteer 通过 page.cookies() / page.setCookie() 管理 Cookie,通过 page.evaluate() 操作 LocalStorage、SessionStorage 和 IndexedDB,结合 userDataDir 或手动序列化实现会话持久化,利用 browser.createIncognitoBrowserContext() 实现多账户隔离。

三种会话持久化方案的对比:

方案适用场景优点缺点
userDataDir长期保持登录态最简单,自动持久化所有数据数据量大,不易清理
手动序列化 Cookie + Storage精确控制需要持久化的数据灵活可控,文件小需要手动处理每种存储
Incognito Context + 手动保存多账户并行完全隔离,互不干扰上下文关闭后数据丢失

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' } );
javascript
// 删除指定 Cookie(需匹配 name 和 domain) await page.deleteCookie({ name: 'session_id', domain: '.example.com' }); // 清除所有 Cookie const allCookies = await page.cookies(); await page.deleteCookie(...allCookies);

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 相同,但数据仅在当前标签页生命周期内有效,关闭标签页即清除:

javascript
await 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 异步操作:

javascript
const 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 等所有用户数据保存到该目录,下次启动自动恢复:

javascript
const browser = await puppeteer.launch({ userDataDir: './user_data/session1' // 指定持久化目录 }); const page = await browser.newPage(); await page.goto('https://example.com'); // 所有登录状态、Cookie、Storage 自动持久化到磁盘

这种方式最简单,但要注意:目录会随使用逐渐增大,长期运行需要定期清理。

当只需要保存部分数据时,手动序列化更精确:

javascript
async 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,互不干扰:

javascript
const 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 和手动序列化:

javascript
async 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。

标签:Puppeteer