BotBrowser 自定义 HTTP 请求头:参数、配置文件或 CDP
通过 --bot-custom-headers、配置文件 configs 或 CDP 命令添加应用专用请求头,同时让 User-Agent 和 Sec-CH-UA 等由配置文件管理的请求头保持一致。
BotBrowser Team
自定义 HTTP 请求头让 BotBrowser 会话在配置文件管理的标准请求头之外,附带令牌、构建标记等应用值。BotBrowser 负责按你的定义发送这些请求头,但不能让服务器接受它们、替你通过 CORS 预检,也不能替换配置文件管理的标准请求头。
要点摘要
为每个请求头指定唯一的来源:静态集合用 --bot-custom-headers 参数,随配置文件走的集合用 configs.customHeaders,运行时变化的值用浏览器级 CDP 命令。然后在回显端点确认这些请求头到达,且 User-Agent 与 Sec-CH-UA 仍与配置文件一致。
本文目录
- 标准请求头与自定义请求头
- 在参数、配置文件与 CDP 命令之间选择
- 用启动参数设置静态请求头集合
- 运行时修改请求头与按浏览器上下文设置
- 验证请求头并处理 CORS 预检
- 可复现的请求头检查
- BotBrowser 的适用范围与边界
标准请求头与自定义请求头
浏览器发出的每个 HTTP 请求都带有一组请求头字段。其中一部分描述浏览器本身:User-Agent 给出浏览器及其版本,Sec-CH-UA 及其同类字段以结构化方式描述品牌和平台,Accept-Language 列出语言偏好。另一部分描述应用:授权令牌、构建编号、租户名称,或者网关在请求到达应用之前读取的路由提示。第一类应当与你选定的浏览器身份保持一致,第二类则完全由你定义。
这正是自定义请求头单独成为一项功能的原因。加载配置文件后,BotBrowser 会让 User-Agent、Sec-CH-UA 等由配置文件管理的标准请求头与该配置文件中的浏览器身份保持一致。自定义请求头与它们并列存在:它们用于添加应用专用的值。与现有请求头同名的自定义请求头可能会覆盖它,因此请不要把 User-Agent、Sec-CH-UA 等由配置文件管理的名称放进自定义集合。如果页面应当表现为某一种浏览器,那么一个与之矛盾的多余请求头就会制造出日后很难解释的不一致,所以规则很简单:标准请求头来自配置文件,应用请求头来自你。
请求头字段的语义由 RFC 9110 定义。字段名不区分大小写,所以 X-App-Version 与 x-app-version 是同一个字段。字段值是一个字符串;当同一个字段名出现多次时,接收方可以把这些行当作一个以逗号分隔的列表。这些细节在实践中很重要,因为在 JSON 对象里看起来没有问题的请求头集合,仍可能被一个会规范化字段名、合并重复项或拒绝特殊字符的网关以不同方式解读。
自定义请求头的典型用途有以下几类:
- 认证与授权提示,例如网关要求每次调用都携带的 API 密钥或 bearer 令牌。
- 应用标记,例如后端用于路由的构建号、租户标识或功能开关名称。
- 测试与预发布开关,例如在你自己拥有的测试环境中选择灰度部署,或开启详细响应的请求头。
- 移动端 Web 视图约定,例如网站期望应用容器发送的
X-Requested-With值。
自定义请求头不适合用来描述网络路径。X-Forwarded-For 和 X-Real-IP 之类的请求头属于代理和负载均衡基础设施,配置正确的服务器不会采信浏览器操作者设置的值。从浏览器端设置它们只会造成混乱,请改用应用专用的名称。
请求头的值由操作者负责。携带凭据的请求头应当像其他机密一样处理:不要写入共享日志,按后端规定的周期轮换,并且只发送给应当接收它的来源。BotBrowser 会把你配置的请求头发送到 HTTP 和 HTTPS 请求,所以你配置的范围就是暴露的范围。
在参数、配置文件与 CDP 命令之间选择
BotBrowser 文档记录了三种定义自定义请求头的方式,选择哪一种取决于集合变化的频率以及由谁维护。三种方式都需要 PRO 许可证。
第一种是 --bot-custom-headers 启动参数。它接收一个 JSON 对象,键是请求头名称,值是字符串。对静态集合来说它最简单:请求头在浏览器启动时确定,从第一个请求起就生效,启动命令是读取它们的唯一位置。它适合定时任务、CI 运行,或者总是用同一个应用标记调用同一个后端的服务。
第二种是配置文件字段 configs.customHeaders。此时请求头存放在配置文件中,而不是启动命令里。当请求头集合属于配置文件而不是某次运行时,这种方式很有用,例如一个配置文件始终代表一个应用容器,并且在任何使用它的地方都应携带同样的标记。请求头随配置文件一起移动,所以任何加载该文件的启动器都无需额外参数就能得到相同的行为。
第三种是浏览器级 CDP 命令:BotBrowser.setCustomHeaders、BotBrowser.getCustomHeaders、BotBrowser.addCustomHeader、BotBrowser.removeCustomHeader 和 BotBrowser.clearCustomHeaders。它们在浏览器运行期间修改请求头集合,无需重启。文档把 CDP 命令列为三种方式中最灵活的一种,因此在会话期间轮换的令牌,或需要在两次导航之间变化的请求头,都自然适合用它。
一份简短的选择指南:
- 集合在一次运行中不变:使用启动参数。
- 集合属于某个配置文件并应随它移动:使用
configs.customHeaders。 - 集合在浏览器打开期间变化:对浏览器级会话使用 CDP 命令。
- 不同浏览器上下文需要不同集合:通过按上下文传入的参数设置,见后文。
三种方式可以同时存在,所以最好事先决定某个请求头由哪一种方式负责。文档按优先级列出它们:CDP 命令在前,其次是命令行参数,最后是配置文件字段。即便如此,如果同一个名称在两处定义,你仍然应当在服务器报错之前确认实际发送的是哪个值。一个实用的约定是:稳定的标记放在配置文件或启动参数里,CDP 命令只留给真正动态的值。
阅读文档时还需要区分一个词。被 setCustomHeaders 替换的是之前的自定义集合,而不是配置文件管理的标准请求头。addCustomHeader 和 removeCustomHeader 只影响单个自定义条目,clearCustomHeaders 会清空自定义集合。不要用它们删除或改写 User-Agent 或 Sec-CH-UA,这些名称应交给配置文件管理。
对于 Cookie,完全不要使用 Cookie 请求头。BotBrowser 有专门的 --bot-cookies 参数,支持按上下文范围导入,这是处理 Cookie 状态的受支持途径。把 Cookie 当作自定义请求头,会跳过 Cookie 本应具备的作用域和过期处理。
用启动参数设置静态请求头集合
对于静态请求头集合,把 JSON 对象传给启动参数。在 shell 中,值两侧的单引号能保持 JSON 完整:
chrome --bot-profile="path/to/profile.enc" \
--bot-custom-headers='{"X-App-Version":"2.1.0","X-Tenant":"acme-test"}'
从代码启动时,引号规则会变化。shell 示例中的单引号只为 shell 服务。在 JavaScript 中,参数会直接传给进程,所以值必须是纯 JSON 字符串。在外面再加引号,引号就会变成值的一部分,浏览器收到的就不再是合法的 JSON。
const headers = { 'X-App-Version': '2.1.0', 'X-Tenant': 'acme-test' };
// 正确:参数由参数名、等号和 JSON 文本组成
args.push('--bot-custom-headers=' + JSON.stringify(headers));
// 错误:单引号会成为值的一部分
args.push(`--bot-custom-headers='${JSON.stringify(headers)}'`);
完整的 Playwright 启动如下:
const { chromium } = require('playwright-core');
(async () => {
const headers = { 'X-Auth': 'token123', 'X-Client': 'webapp' };
const browser = await chromium.launch({
executablePath: 'path/to/botbrowser/chrome',
headless: true,
args: ['--bot-profile=path/to/profile.enc', '--bot-custom-headers=' + JSON.stringify(headers)],
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
Puppeteer 版本只有启动调用不同:
const puppeteer = require('puppeteer-core');
(async () => {
const headers = { 'X-Auth': 'token123' };
const browser = await puppeteer.launch({
executablePath: 'path/to/botbrowser/chrome',
headless: true,
args: ['--bot-profile=path/to/profile.enc', '--bot-custom-headers=' + JSON.stringify(headers)],
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
如果你希望请求头随配置文件保存,可以把同样的对象放在配置文件的 configs.customHeaders 下。该值是由字符串键值对组成的普通对象,与启动参数完全相同,默认值是空对象。当配置文件和启动参数同时存在值时,请先按验证一节所述在回显端点上检查结果,再决定依赖哪一个。
首次启动之前,有几个细节值得先确定:
- 只使用字符串值。数字和布尔值应写成字符串,因为请求头的值是文本。
- 名称只使用普通的标记字符。名称中的空格和标点会被许多服务器以及路径上的中间设备拒绝。
- 控制总大小。文档没有设定具体上限,但每个请求头都会随每个请求发送,而服务器和网关各自对请求头大小设有限制。
- 不要把机密写入保存在共享脚本或进程列表中的启动命令。请在启动时从环境变量或机密存储中读取。
在 Android Web 视图类的场景中,文档展示了用同一个参数重现应用容器会发送的 X-Requested-With 值。模式完全一致:通过参数传入一个 JSON 对象,应用于 HTTP 和 HTTPS 请求。
运行时修改请求头与按浏览器上下文设置
当值在会话期间变化时,静态参数就不够了,例如需要刷新的短期令牌,或在流程的两个阶段之间应当不同的请求头。为此 BotBrowser 提供了 CDP 命令。它们必须发送到浏览器级会话,而不是页面级会话。
使用 Playwright 时,打开浏览器级会话并发送命令:
const cdpSession = await browser.newBrowserCDPSession();
// 替换整个自定义集合
await cdpSession.send('BotBrowser.setCustomHeaders', {
headers: { 'X-Requested-With': 'com.example.app', 'X-Session': 'first' },
});
// 读取当前自定义集合
const current = await cdpSession.send('BotBrowser.getCustomHeaders');
console.log(current.headers);
// 添加或更新单个条目
await cdpSession.send('BotBrowser.addCustomHeader', {
name: 'X-Session',
value: 'refreshed',
});
// 移除单个条目,或清空自定义集合
await cdpSession.send('BotBrowser.removeCustomHeader', { name: 'X-Session' });
await cdpSession.send('BotBrowser.clearCustomHeaders');
使用 Puppeteer 时,浏览器级会话来自浏览器目标:
const cdpSession = await browser.target().createCDPSession();
await cdpSession.send('BotBrowser.setCustomHeaders', {
headers: { 'X-Requested-With': 'com.example.app' },
});
await cdpSession.send('BotBrowser.addCustomHeader', {
name: 'X-Token',
value: 'my-token',
});
如果把其中任何一个命令发送到页面级会话,调用会失败,并返回 ProtocolError: 'BotBrowser.setCustomHeaders' wasn't found。这个错误通常说明会话是从页面而不是从浏览器创建的。换成浏览器级会话后,命令就能被找到。
这五个命令覆盖了自定义集合的完整生命周期:
setCustomHeaders用你传入的对象替换全部自定义请求头。getCustomHeaders返回当前的自定义集合,便于记录你即将依赖的内容。addCustomHeader按名称添加或更新单个条目。removeCustomHeader按名称移除单个条目。clearCustomHeaders移除所有自定义请求头。
一个可靠的做法是:先应用新的集合,再开始依赖它的导航;需要确认当前状态时,用 getCustomHeaders 读回集合。
不同任务有时需要在同一个浏览器里使用不同的请求头。BotBrowser 支持按浏览器上下文分配请求头。按上下文的参数通过 BotBrowser.setBrowserContextFlags 传入,自定义请求头参数就是上下文可以携带的参数之一:
const client = await browser.target().createCDPSession();
const contextA = await browser.createBrowserContext();
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: contextA._contextId,
botbrowserFlags: ['--bot-profile=path/to/profile.enc', '--bot-custom-headers={"X-Requested-With":"com.app.one"}'],
});
const contextB = await browser.createBrowserContext();
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: contextB._contextId,
botbrowserFlags: ['--bot-profile=path/to/profile.enc', '--bot-custom-headers={"X-Requested-With":"com.app.two"}'],
});
这里每个上下文都带有自己的请求头集合,所以同时打开的两个页面会发送不同的应用标记。仍需记住同样的引号规则:数组中的每一项是一个参数,所以 JSON 文本不加 shell 引号。
验证请求头并处理 CORS 预检
请务必在一个会回显所收到内容的端点上验证请求头配置。快速检查可以使用 httpbin 这样的公共回显服务,而你自己控制的预发布端点更好,因为它还能显示你自己的网关如何处理这些值。
const page = await context.newPage();
await page.goto('https://httpbin.org/headers');
const headersText = await page.textContent('body');
console.log('Received headers:', headersText);
一次好的验证包含三个部分:
- 确认你配置的每个自定义请求头都出现在响应中,且值与你设置的完全一致。
- 确认
User-Agent和Sec-CH-UA仍与已加载的配置文件一致,这说明标准集合没有被改动。 - 打开浏览器开发者工具的网络面板,确认页面发出的各类请求上都带有这些请求头,例如文档、脚本、图片和 fetch 调用。
如果某个请求头没有出现,原因通常是三者之一。JSON 可能无效,最常见的是前面提到的引号错误。命令可能发送到了页面级会话而不是浏览器级会话。或者浏览器与回显端点之间的某个中间设备删除或重命名了该字段,这正是与你自己控制的服务直接对比很有价值的原因。
需要理解的第二个行为是 CORS。浏览器对跨源请求和同源请求的处理不同。对许多跨源请求,浏览器会先用 OPTIONS 方法发送一个预检请求。它携带 Access-Control-Request-Method 和 Access-Control-Request-Headers,列出真实请求打算使用的方法和不在安全列表中的请求头。服务器用 Access-Control-Allow-Origin、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 回答,只有回答覆盖该请求,浏览器才会继续。MDN 的 CORS 指南描述了完整的交互,包括 Access-Control-Max-Age,它让浏览器在一段时间内复用预检的答复。
非标准请求头通常是触发条件。少数请求头,例如 Accept、Accept-Language 和 Content-Language,在安全列表中,本身不会引发预检。X-Auth-Token 或 X-Tenant 这样的请求头则不在其中。当浏览器把这类请求头加到跨源请求上时,可能触发预检。如果目标服务器没有在 Access-Control-Allow-Headers 中列出该请求头名称,请求可能失败。BotBrowser 文档也这样说明:添加非标准请求头可能在某些网站上触发预检,目标服务器必须允许它们。
这会带来几个实际后果:
- 如果目标服务器由你拥有,请把请求头名称加入它的
Access-Control-Allow-Headers响应,并决定预检答复可以缓存多久。 - 如果服务器不属于你,你无法改变它的答复。请换一个请求头,或换一种方式,例如把值放在服务器已经接受的位置。
- 同源请求不需要预检,所以针对你自己源的测试可能通过,而同一个请求头在另一个源上失败。请针对你真正要使用的源测试。
- 允许该请求头的预检答复并不等于授权该请求。认证和授权仍是服务器单独做出的决定。
大小也很重要。服务器、网关和 CDN 都会限制请求头的总大小。自定义请求头集合过大,或某个值过长,可能导致与 CORS 无关、也与 BotBrowser 无关的拒绝。如果请求头集合变大后请求开始失败,请先缩小集合,再去别处排查。
BotBrowser 的适用范围与边界
BotBrowser 支持通过 --bot-custom-headers、配置文件字段 configs.customHeaders 以及浏览器级的 BotBrowser.*CustomHeaders CDP 命令添加自定义 HTTP 请求头。这些请求头应用于 HTTP 和 HTTPS 请求,并且可以按浏览器上下文分配,因此团队可以发送一组有文档说明的应用请求头,同时由配置文件让 User-Agent 和 Sec-CH-UA 等标准请求头保持一致。请求头控制需要 PRO 许可证。BotBrowser 不能让服务器接受、授权或信任所添加的请求头,不能跳过 CORS 预检或服务器实施的请求头与大小限制,也不能替代你自己的请求头和认证设计。请求头的值由读取它的应用决定,所以服务器一侧仍由你负责。
实际上,这意味着把这项功能当作一套已有请求头设计的传输通道。先确定后端期望哪些名称、哪些来源可以接收它们、令牌如何签发和轮换,以及被拒绝的请求应当是什么样子。然后用启动参数、配置文件字段或 CDP 命令从浏览器端交付这套设计。如果设计合理,交付很简单;如果设计不清晰,选择哪个参数都无法弥补。
有几条边界值得记住:
- 用自定义请求头添加应用的值,不要把
User-Agent、Sec-CH-UA等由配置文件管理的标准请求头放进自定义集合。 X-Forwarded-For等代理基础设施请求头不在本指南建议的范围内,因为它们描述的是浏览器无法控制的网络路径。- Cookie 属于 Cookie 参数,而不是自定义的
Cookie请求头。 - 你添加的每个请求头,对接收它的服务器以及路径上的任何中间设备都是可见的,请把这组请求头视为对这些方公开。
关于周边配置,代理配置说明如何设置网络路由,User-Agent 与 Client Hints说明标准身份请求头如何跟随配置文件,多账户浏览器隔离则介绍当每个上下文都需要自己的请求头集合时如何让各上下文彼此隔离。
可复现的请求头检查
下面的实测使用 loopback HTTP fixture,因此可以在不把凭据或测试值发送给第三方服务的情况下重复观察。fixture 提供 /echo,从 /redirect 返回 302,并对 OPTIONS 预检返回请求的允许列表。这个命令故意比完整浏览器启动更小:它验证协议和排障路径,而 BotBrowser 参数与 CDP 调用仍然是上文所述的实际投递方式。
echo custom=custom-http-headers-20261017 ua=fixture-UA/1.0 sec=\"Fixture\";v=\"1\"
redirect status=200 custom=custom-http-headers-20261017
preflight status=204 allow=x-test-run
第一行说明应用值、user-agent 字段以及形似 client hints 的字段一起到达了受控接收端。第二行很重要,因为重定向可能改变来源,也可能根据浏览器和服务器策略移除请求头;这次 fixture 在同一主机的重定向后仍保留了自定义值。第三行是服务器响应,不是浏览器授权:allow=x-test-run 只表示接收端把这个名称放进预检允许列表,并不代表最终请求已经通过认证。
把输出当作排障决策树使用。如果 echo 行缺少 custom,先检查 JSON 引号和参数作用域。如果 echo 成功但跨源请求在应用请求之前停止,检查 Access-Control-Allow-Headers 与来源策略。如果首个请求成功而重定向行发生变化,对比重定向目标、上下文参数以及可能重写字段的网关。fixture 记录 User-Agent 和形似 Sec-CH-UA 的值,只是为了显示接收端看到的字段;它不是检测器评分,也没有声称 Node.js 就是 BotBrowser 引擎。证据文件记录 Node 二进制摘要和稳定配置文件摘要,便于重做同一个本地协议检查。
相对参考文档新增的内容
参考页说明配置选项。本节增加对比:在初始请求、CORS 预检和重定向中分别检查配置文件值、启动值和上下文级 CDP 值。只有某一跳不一致时,应先检查请求作用域或服务器策略,而不是直接归因于浏览器。
来源
以上关于请求头、CORS 和 BotBrowser 的陈述所使用的公开资料:
实用结论
按集合变化的频率选择投递方式,把 Cookie 留给 --bot-cookies,并把 X-Forwarded-For 这类代理请求头排除在自定义集合之外。只有在自己控制的端点按原值回显,并且同源请求与触发预检的跨源请求都通过时,才算请求头生效。缺失时先查 JSON 引号和会话层级,再怀疑服务器。
本文属于指南: 代理配置总览:浏览器网络决策图