Puppeteer Browser.cookies() 深度解析:从方法签名到 CDP/BiDi 底层实现的浏览器级 Cookie 读取
本篇指南基于 Puppeteer 官方 API 文档中的 Browser.cookies() 方法展开,讲解如何获取默认浏览器上下文中的全部 Cookie,并结合开源仓库源码剖析该“快捷方法”在 CDP 与 WebDriver BiDi 两种协议下的真实调用链、Cookie 数据模型的字段语义,以及与页面级 page.cookies() 的边界差异。读完本文,你可以掌握浏览器级 Cookie 读取的完整实现路径,并在自动化测试、会话管理、爬虫状态保持等场景下正确选择 browser.cookies()、browserContext.cookies() 或 page.cookies()。
方法定义与签名
Browser.cookies() 是 Puppeteer 提供的浏览器级(browser-level)Cookie API,其官方定义如下(见 docs/api/puppeteer.browser.cookies.md):
Returns all cookies in the default BrowserContext.(返回默认 BrowserContext 中的全部 Cookie)
方法签名与返回值:
class Browser {
cookies(): Promise<Cookie[]>;
}
- 返回值:
Promise<Cookie[]>,其中 Cookie 是描述单个 Cookie 对象的接口; - 文档 Remarks 说明:该方法本质上是
browser.defaultBrowserContext().cookies()的快捷方式(shortcut)。
这一点在源码中得到逐字印证。在 packages/puppeteer-core/src/api/Browser.ts 中:
/**
* Returns all cookies in the default {@link BrowserContext}.
*
* @remarks
*
* Shortcut for
* {@link BrowserContext.cookies | browser.defaultBrowserContext().cookies()}.
*/
async cookies(): Promise<Cookie[]> {
return await this.defaultBrowserContext().cookies();
}
也就是说,browser.cookies() 本身不包含任何协议逻辑,它只是把请求转发给默认上下文(defaultBrowserContext())的 BrowserContext.cookies() 抽象方法,由各协议的具体实现完成真正的查询。这也是理解整套浏览器级 Cookie API 的关键:Puppeteer 的 Browser 类对 Cookie 做“上下文级”管理,而真正的数据存取发生在 BrowserContext 层。
与 cookies() 并列的另外三个浏览器级快捷方法(同在 api/Browser.ts 中定义)构成一组完整的读写删接口:
| 方法 | 作用 | 等价调用 |
|---|---|---|
browser.cookies() |
读取默认上下文全部 Cookie | browser.defaultBrowserContext().cookies() |
browser.setCookie(...cookies) |
向默认上下文写入 Cookie | browser.defaultBrowserContext().setCookie(...) |
browser.deleteCookie(...cookies) |
删除指定 Cookie | browser.defaultBrowserContext().deleteCookie(...) |
browser.deleteMatchingCookies(...filters) |
按过滤条件批量删除 | browser.defaultBrowserContext().deleteMatchingCookies(...) |
对应文档分别为 browser.setCookie()、browser.deleteCookie()、browser.deleteMatchingCookies()。当你需要隔离多个会话时,应改用 browser.createBrowserContext() 创建独立上下文,并直接在该上下文上调用这些方法,而不是使用浏览器级快捷方式。
底层实现:CDP 模式下的调用链
BrowserContext.cookies() 是一个抽象方法,Puppeteer 在 CDP(Chrome DevTools Protocol)和 WebDriver BiDi 两个后端各有一套实现。
CDP 后端
CDP 实现位于 packages/puppeteer-core/src/cdp/BrowserContext.ts:
override async cookies(): Promise<Cookie[]> {
const {cookies} = await this.#connection.send('Storage.getCookies', {
browserContextId: this.#id,
});
return cookies.map(cookie => {
return {
...cookie,
partitionKey: cookie.partitionKey
? {
sourceOrigin: cookie.partitionKey.topLevelSite,
hasCrossSiteAncestor: cookie.partitionKey.hasCrossSiteAncestor,
}
: undefined,
};
});
}
从源码结构看,可以提炼出三个关键事实:
- 底层 CDP 命令是
Storage.getCookies,且请求体携带browserContextId。这解释了为什么浏览器级 API 天然以“上下文”为粒度——CDP 的存储域(Storage domain)本身就是按浏览器上下文划分 Cookie 存储的。 - 返回结果会经过一次字段映射:CDP 原始的
partitionKey使用topLevelSite字段名,而 Puppeteer 统一转换为自己的 CookiePartitionKey 结构{ sourceOrigin, hasCrossSiteAncestor }。这属于“Puppeteer 公共模型与协议私有模型之间的适配层”,调用方无需关心协议差异。 - 无
partitionKey的 Cookie 会被显式置为undefined,保证返回结构一致,便于 JSON 序列化或断言比较。
同文件中的 setCookie()(第 163-176 行)则发送 Storage.setCookies 命令,并在写入前把 Puppeteer 的 partitionKey 与 sameSite 反向转换为 CDP 格式——这提示我们:读写两侧都存在模型转换逻辑,Cookie 的 sameSite、partitionKey 等字段是跨协议适配的重点。
WebDriver BiDi 后端
BiDi 实现位于 packages/puppeteer-core/src/bidi/BrowserContext.ts:
override async cookies(): Promise<Cookie[]> {
const cookies = await this.userContext.getCookies();
return cookies.map(cookie => {
return bidiToPuppeteerCookie(cookie, true);
});
}
与 CDP 直接发送命令不同,BiDi 后端通过 userContext.getCookies() 获取原始数据,再由 bidiToPuppeteerCookie 将 BiDi 的 Cookie 模型转换为 Puppeteer 统一的 Cookie 模型。两套实现对外表现一致:无论使用 protocol: 'cdp' 还是 protocol: 'webdriverBiDi' 启动浏览器,browser.cookies() 返回的都是同一形状的 Cookie[] 数组。
Cookie 数据模型:字段逐个解读
browser.cookies() 返回的每个元素都是 Cookie 接口对象。Cookie 继承自 CookieData(用于 setCookie() 的参数类型),并在其基础上补充了若干“只读描述”字段。
Cookie 扩展字段(Cookie 接口独有)
| 属性 | 类型 | 说明 |
|---|---|---|
expires |
number |
Cookie 过期时间,表示距离 UNIX 纪元的秒数;会话 Cookie 为 -1 |
partitionKeyOpaque |
boolean(可选) |
Cookie 的分区键是否为 opaque;仅 Chrome 支持 |
path |
string |
Cookie 的路径 |
secure |
boolean |
是否为 Secure Cookie |
session |
boolean |
是否为会话 Cookie |
size |
number |
Cookie 大小 |
CookieData 基础字段(继承而来)
CookieData 定义了在浏览器级 Cookie API 中设置 Cookie 所需的参数对象,其已确认包含的字段有:
| 属性 | 类型 | 说明 |
|---|---|---|
domain |
string |
Cookie 所属域名 |
name |
string |
Cookie 名称 |
expires |
number(可选) |
过期时间;不设置则为会话 Cookie |
httpOnly |
boolean(可选) |
是否为 HttpOnly Cookie |
partitionKey |
CookiePartitionKey | string(可选) |
分区键,可用于第三方 Cookie 分区(CHIPS)相关场景 |
从 CDP 后端的 setCookie() 实现(cdp/BrowserContext.ts 第 163-176 行)中引用的字段还可以推断,完整的 Cookie 模型还支持 path、secure、sameSite、url、sourceScheme、priority 等属性——这些字段在写入时会被逐项转换并下发给浏览器,读取时(如 sourceScheme、priority)属于 Chrome 特有属性。若你要精确核对每个字段,建议直接查看仓库中 docs/api/puppeteer.cookiedata.md 与 docs/api/puppeteer.cookie.md 的完整属性表。
实战示例
下面的示例展示完整的“写入 → 读取 → 校验 → 清理”流程,全部使用浏览器级 API:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
// 1. 向默认浏览器上下文写入 Cookie
await browser.setCookie({
name: 'session_id',
value: 'abc123',
domain: 'example.com',
path: '/',
httpOnly: true,
secure: true,
sameSite: 'Lax',
});
// 2. 读取默认上下文中的全部 Cookie
const cookies = await browser.cookies();
for (const cookie of cookies) {
console.log(cookie.name, cookie.domain, cookie.path, {
httpOnly: cookie.httpOnly,
secure: cookie.secure,
session: cookie.session,
size: cookie.size,
});
}
// 3. 清理:按条件批量删除
await browser.deleteMatchingCookies({name: 'session_id'});
console.log(await browser.cookies()); // []
await browser.close();
几点使用说明:
browser.cookies()不带 URL 参数,它返回默认上下文的“全量” Cookie;如果你只想看某个页面/域下的 Cookie,应使用页面级 APIpage.cookies(...urls);- 由于底层按
browserContextId查询,通过browser.createBrowserContext()创建的隔离上下文中的 Cookie 不会出现在browser.cookies()的结果里,必须对具体上下文调用context.cookies(); - 仓库测试 test/src/browsercontext-cookies.test.ts 覆盖了空上下文返回
[]、写入后断言字段(domain/expires/httpOnly/sameSite 等)、以及删除后清空等场景,可作为你编写断言的参考写法。
边界辨析:browser.cookies() 与 page.cookies()
Puppeteer 中“浏览器级”与“页面级”两套 Cookie API 经常被混淆,结合源码可以明确它们的差异:
| 维度 | browser.cookies() |
page.cookies(...urls) |
|---|---|---|
| 作用域 | 默认浏览器上下文的全部 Cookie | 指定 URL(或当前页面 URL)匹配的 Cookie |
| 参数 | 无 | 可选的 ...urls: string[] |
| CDP 命令 | Storage.getCookies(按 browserContextId) |
Network.getCookies(按 urls) |
| 实现位置 | cdp/BrowserContext.ts#L146-L161 | cdp/Page.ts#L701-L717 |
CDP 后端的页面级实现(packages/puppeteer-core/src/cdp/Page.ts)细节值得注意:
override async cookies(...urls: string[]): Promise<Cookie[]> {
const originalCookies = (
await this.#primaryTargetClient.send('Network.getCookies', {
urls: urls.length ? urls : [this.url()],
})
).cookies;
const unsupportedCookieAttributes = ['sourcePort'];
// ... 过滤不支持的属性后返回
}
- 不传 URL 时默认查询当前页面 URL 对应的 Cookie;
- 会主动剔除当前协议版本不支持的属性(如
sourcePort),保证返回模型稳定。
因此经验法则是:管理 Cookie(导入会话、批量清理、跨页面共享)用 browser.cookies() / browserContext.cookies();断言某个页面可见的 Cookie 用 page.cookies(url)。测试代码 test/src/cookies.test.ts 与 test/src/defaultbrowsercontext.test.ts 分别验证了这两类 API 的行为。
测试用例与行为验证
仓库测试为 browser.cookies() 所属的上下文级 API 提供了行为基线(见 test/src/browsercontext-cookies.test.ts):
- 新建隔离上下文后,
context.cookies()应返回[]——证明上下文间 Cookie 完全隔离; - 调用
context.setCookie()后读取,断言domain、expires、httpOnly、sameSite等字段与写入值一致; - 删除(
deleteCookie/deleteMatchingCookies)后再次读取,长度归零。
这些用例与源码中的 Storage.getCookies 调用链相互印证,说明浏览器级 Cookie API 的语义边界就是 browserContextId,而不是“整个浏览器进程”。
总结
Browser.cookies()的签名是cookies(): Promise<Cookie[]>,语义为“返回默认浏览器上下文中的全部 Cookie”,源码上仅是defaultBrowserContext().cookies()的一行转发(api/Browser.ts#L691-L693);- CDP 后端通过
Storage.getCookies+browserContextId查询并做partitionKey字段映射(cdp/BrowserContext.ts#L146-L161);BiDi 后端通过userContext.getCookies()获取后统一转换模型(bidi/BrowserContext.ts#L374-L379),两种协议对外返回同一Cookie[]结构; - 返回的
Cookie对象在CookieData(domain、name、httpOnly、partitionKey等)之上扩展了expires、path、secure、session、size、partitionKeyOpaque(仅 Chrome)等描述字段; - 需要精确到 URL/页面粒度的查询时,改用
page.cookies(...urls)(Network.getCookies命令);需要多会话隔离时,使用createBrowserContext()并在具体上下文上操作 Cookie; - 完整的 Cookie 使用指引可进一步参考仓库的 docs/guides/cookies.md 与 BrowserContext.cookies() 文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00