Puppeteer `Page.setUserAgent()` 完全指南:覆盖 User-Agent 与 User-Agent Client Hints
Page.setUserAgent() 是 Puppeteer 中用于自定义页面发送请求时 User-Agent 字符串及浏览器客户端提示(User-Agent Client Hints)的核心方法,常用于伪装浏览器标识、模拟移动端设备、规避站点针对桌面 Chrome 的差异化检测等场景。本文以 官方 API 文档 为骨架,结合仓库内 Chrome DevTools Protocol(CDP)与 WebDriver BiDi 两条实现链路及完整测试用例,讲解两个重载签名、各参数含义、底层生效机制与实战技巧,帮助你彻底掌握该 API。
方法概览:为什么需要手动设置 User-Agent
现代网站普遍通过 navigator.userAgent 与请求头中的 User-Agent 判断访问端类型,并据此提供桌面/移动版页面、限制自动化访问或注入差异化内容。而在最新浏览器中,User-Agent Client Hints 体系(通过 navigator.userAgentData 暴露)让站点可以获得更细粒度的品牌、架构、平台等信息,单纯改写 UA 字符串已不足以"骗过"依赖 Client Hints 的服务端检测。
Page.setUserAgent() 之所以重要,在于它同时覆盖两条通道:
- 请求头
User-Agent(通过协议层Network.setUserAgentOverride等实现); - 客户端提示元数据(architecture、model、platform、platformVersion、mobile 等),影响
navigator.userAgentData及其 High-Entropy 值。
该方法是 Page 类 的抽象方法,在 Chromium(CDP)与 Firefox(WebDriver BiDi)两套后端上均有完整实现,签名同时声明于公共 API 层。此外它还被 Page.emulate() 在模拟设备描述符时自动调用,因此也是设备模拟机制的地基。
完整签名与参数说明
当前仓库 puppeteer 与 puppeteer-core 版本为 25.8.0(见 packages/puppeteer/package.json 与 packages/puppeteer-core/package.json)。方法声明位于 公共抽象类,存在两个重载:
重载一:位置参数形式(已废弃)
abstract setUserAgent(
userAgent: string,
userAgentMetadata?: Protocol.Emulation.UserAgentMetadata,
): Promise<void>;
| 参数 | 类型 | 说明 |
|---|---|---|
userAgent |
string |
本页面要使用的具体 User-Agent 字符串 |
userAgentMetadata |
Protocol.Emulation.UserAgentMetadata |
(可选) 对应的客户端提示元数据 |
返回: Promise<void> —— 当 User-Agent 设置完成后 resolve。
⚠️ 废弃提示:官方文档明确标注该重载已经 obsolete,应改用下方重载二(options 对象形式)。在源码中对应注释
@deprecated Use Page.(setUserAgent:2) instead(见 Page.ts)。
重载二:options 对象形式(推荐)
abstract setUserAgent(options: {
userAgent?: string;
userAgentMetadata?: Protocol.Emulation.UserAgentMetadata;
platform?: string;
}): Promise<void>;
| 参数 | 类型 | 说明 |
|---|---|---|
options |
{ userAgent?: string; userAgentMetadata?: ...; platform?: string } |
包含 UA 及可选的 User-Agent 元数据的对象 |
返回: Promise<void> —— 当 User-Agent 设置完成后 resolve。
options 形式支持三个字段,其中 userAgent 与 platform 均为可选,意味着你可以只覆盖其中之一而保持其余值不变:
| 字段 | 类型 | 含义 | 关键行为 |
|---|---|---|---|
userAgent |
string |
期望的完整 UA 字符串 | 缺省时继承浏览器当前默认 UA(Browser.userAgent()) |
userAgentMetadata |
Protocol.Emulation.UserAgentMetadata |
UA Client Hints 元数据,控制 navigator.userAgentData |
可省略 |
platform |
string |
需要模拟的平台名,影响 navigator.platform 等 |
可省略;传空字符串表示"不覆盖平台"(见下文 BiDi 分支的特殊处理) |
基础用法示例
以下代码在导航之前为当前页面设置自定义 UA,使该页面此后发起的请求(含子框架请求)都携带该字符串:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 推荐:options 对象形式
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
);
// 若希望同步覆盖平台标识
await page.setUserAgent({
userAgent: 'Mozilla/5.0 ... Chrome/131.0.0.0 Safari/537.36',
platform: 'Win32',
});
await page.goto('https://example.com');
await browser.close();
位置参数重载(
setUserAgent('...'))虽仍可调用,但已被标记 deprecated,新代码应统一使用 options 形式。若选择使用已废弃重载并传入第二个参数userAgentMetadata,请务必让其中的字段与第一个 UA 字符串保持自洽,否则可能出现 UA 与 Client Hints 相互矛盾。
深入参数一:userAgentMetadata 与 Client Hints
userAgentMetadata 的类型为 Protocol.Emulation.UserAgentMetadata,即 CDP Emulation 域定义的 User-Agent 元数据结构。它负责将浏览器渲染出的 navigator.userAgentData(以及 getHighEntropyValues() 可读取的 High-Entropy 字段)一并覆盖。
仓库中的 集成测试(测试名 should work with additional userAgentMetdata)验证了该参数的生效范围:
await page.setUserAgent('MockBrowser', {
architecture: 'Mock1',
mobile: false,
model: 'Mockbook',
platform: 'MockOS',
platformVersion: '3.1',
});
// 页面内:
// navigator.userAgentData.mobile === false
// navigator.userAgentData.getHighEntropyValues(['architecture','model','platform','platformVersion'])
// → { architecture: 'Mock1', model: 'Mockbook', platform: 'MockOS', platformVersion: '3.1' }
// 且请求头 User-Agent === 'MockBrowser'
可见该参数会直接影响页面 JS 侧可观察到的 Client Hints(含 High-Entropy 部分),而不只是网络层信息。字段通常在结构上涵盖品牌列表(brands)、architecture、bitness、fullVersionList、mobile、model、platform、platformVersion、wow64 等——仓库测试实际断言了 architecture、model、platform、platformVersion、mobile 五个字段的读写行为,是可靠的参照。
深入参数二:platform 与"只改平台不动 UA"
options.platform 用于单独模拟操作系统平台。测试 should work with platform option without userAgent(见 page.test.ts)证明:只传 platform、不传 userAgent 时,UA 字符串保持不变,但 navigator.platform 会返回模拟值,且实际发出的请求头 User-Agent 也仍为原始 UA:
await page.setUserAgent({platform: 'MockPlatform'});
// navigator.platform === 'MockPlatform'
// navigator.userAgent === 原 UA(未改变)
// 请求头 user-agent === 原 UA
这适用于仅需伪装操作系统、又不想整套更换浏览器标识的场景。
底层原理一:CDP 后端(Chromium)的生效链路
在 Chromium 后端,方法实现位于 cdp/Page.ts。其核心逻辑是按入参形态分派:
if (typeof userAgentOrOptions === 'string') {
// 已废弃位置参数重载 → 仍兼容转发
return await this.#frameManager.networkManager.setUserAgent(
userAgentOrOptions,
userAgentMetadata,
);
} else {
// options 形式:未显式给 userAgent 时回落到浏览器默认 UA
const userAgent = userAgentOrOptions.userAgent ?? (await this.browser().userAgent());
return await this.#frameManager.networkManager.setUserAgent(
userAgent,
userAgentOrOptions.userAgentMetadata,
userAgentOrOptions.platform,
);
}
可以看到,对象形式在 userAgent 缺省时会自动取 Browser.userAgent() 作为兜底,这正是"只覆盖 platform 不影响 UA"的来源。
随后请求落到 NetworkManager.setUserAgent,将三份数据暂存后通过 #applyToAllClients 广播到所有已连接的 CDP 会话(#applyUserAgent,见 NetworkManager.ts),最终执行:
await client.send('Network.setUserAgentOverride', {
userAgent,
acceptLanguage: this.#acceptLanguage,
userAgentMetadata: this.#userAgentMetadata,
platform: this.#platform,
});
几个值得注意的实现细节:
- 对全部目标生效:
#applyToAllClients会把 override 应用到页面关联的所有目标上,因此子框架(iframe)里的请求同样携带自定义 UA——对应测试should work for subframes(page.test.ts)。 - 与 Accept-Language 协同:该方法与 Page.setExtraHTTPHeaders 一样经由 NetworkManager 统一管理,且
#applyUserAgent内还会带上acceptLanguage,说明 UA 与语言等网络层 override 由同一套机制维护。 - 幂等与清理:代码中维护了
nothingToEmulate与#userAgentOverrideApplied标记——当所有 override 字段都未设置时仍需发送一次协议指令以重置先前已应用的覆盖(注释原文:"Still need to send once to reset a previously-applied override"),这保证了重复设置/清空之间的一致性。
底层原理二:WebDriver BiDi 后端(Firefox)的差异
Firefox 走 WebDriver BiDi 协议,实现见 bidi/Page.ts,语义上做了与 CDP 分支对齐的归一化,但存在明显的协议差异:
- 空字符串即"恢复默认":代码先将
userAgent归一化——若为''则转换为null,注释指出 "In WebDriver BiDi null is used to restore the original user agent",再调用browsingContext.setUserAgent(); - platform 通过 Client Hints 变通实现:由于 WebDriver BiDi 规范暂未提供独立的平台覆盖(源码注释注明其受 w3c/webdriver-bidi#1065 待办项影响),实现会把
platform合并进 client hints 元数据,通过browsingContext.setClientHintsOverride()下发;同时''平台被解释为"无覆盖"。
这也解释了仓库为何需要区分两条实现路径:同一份 API,在两种协议下的落地方式与还原语义并不相同。
实战:恢复默认 UA
如果你在同一页面先后执行了多次导航或加载了多个站点,希望清除先前设置的覆盖、恢复浏览器出厂默认 UA,可按测试 should restore original(page.test.ts)中的方式传入空字符串:
// 先覆盖为自定义值
await page.setUserAgent('foobar');
// 之后清除覆盖,恢复原始 UA
await page.setUserAgent('');
// 页面内 navigator.userAgent 与后续请求头 user-agent
// 均恢复为浏览器默认值(与页面初始 UA 一致)
该测试同时断言了页面侧 navigator.userAgent 与网络侧请求头的双重复原。注意还原的是"进入覆盖前"的原始 UA,而非上一次覆盖值。
实战:配合设备描述符模拟移动端
Puppeteer 内置了常用设备的描述符集合(KnownDevices,如 iPhone 6),其中即包含对应设备的 UA。测试 should emulate device user-agent(page.test.ts)展示了直接取用设备 UA 的写法:
import puppeteer, {KnownDevices} from 'puppeteer';
const page = await browser.newPage();
await page.setUserAgent(KnownDevices['iPhone 6'].userAgent);
// 此时页面内 navigator.userAgent 包含 'iPhone'
更常见的做法是直接使用 Page.emulate() 一次模拟设备(viewport + UA + Client Hints),其内部实现正是调用了 setUserAgent({userAgent: device.userAgent, ...})(相关调用见 Page.ts)。若只需修改标识而保留桌面视口,则单独调用 setUserAgent 更合适。
使用建议与注意事项
综合文档声明、两套后端实现与 测试套件 中约 8 个用例,实践中的要点如下:
- 优先使用 options 对象形式:位置参数重载已被官方标记废弃,虽向后兼容,但新代码应迁移到
setUserAgent({userAgent, userAgentMetadata, platform})。 - 调用时机:设置对所有由该页面发起的后续请求生效(含子框架);对已经加载完成的资源,UA 不会追溯改写,通常应在
page.goto()之前调用。但若用于服务端检测验证,改完后再发起的请求(含 reload 或后续导航)都会携带新值。 - 字符串与 Client Hints 必须自洽:若站点通过
navigator.userAgentData或 High-Entropy API 校验设备,仅改 UA 字符串可能暴露不一致;应同步提供匹配的userAgentMetadata。 - 恢复语义按后端区分:空字符串在 BiDi 后端明确转换为
null以恢复原 UA;CDP 后端则依赖协议层 override 的重置机制保证最终一致。跨浏览器(Chromium/Firefox)场景建议以"设置 → 清空 → 校验恢复"的测试行为为准。 - 范围是"当前页面":该方法针对单个 Page 生效,不影响 Browser.userAgent() 或浏览器级别的默认值;新建页面默认仍使用浏览器原始标识。
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 StartedRust0626
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