网络

自定义 HTTP 头:控制浏览器请求头

如何在浏览器引擎级别设置自定义 HTTP 头,以在所有网络请求中实现一致的请求身份。

文档中心

想直接看维护中的产品文档?

这篇文章对应的主题已经有文档中心页面。需要规范流程、当前参数和长期参考时,优先看 docs。

简介

HTTP 头是浏览器发送的每个请求的组成部分。User-AgentAccept-LanguageSec-CH-UAReferer 等头构成了浏览器网络身份的重要部分。当这些头与你的指纹配置文件不一致,或者你需要注入用于身份验证和路由的应用特定头时,标准浏览器 API 就力不从心了。

BotBrowser 通过 --bot-custom-headers 标志和 BotBrowser.setCustomHeaders CDP 命令提供引擎级头控制。与在请求形成后拦截的扩展方案不同,BotBrowser 在请求离开浏览器网络栈之前修改头,覆盖所有请求类型,没有时序间隙。

隐私影响

HTTP 头揭示了大量关于你浏览器环境的信息。User-Agent 字符串标识你的浏览器版本和操作系统。Accept-Language 揭示你的语言偏好,并由此推断你可能的地理区域。Client Hints 头(Sec-CH-UASec-CH-UA-PlatformSec-CH-UA-Mobile)提供关于你浏览器品牌、平台和设备类型的结构化数据。

当这些头与你指纹的其他方面不一致时,这种不一致就很突出。一个报告 Accept-Language: de-DE 但使用美国时区的浏览器,或者 Sec-CH-UA-Platform: "Windows" 但具有 macOS 特定 navigator 属性的浏览器,都会产生追踪系统可以关联的不匹配。

BotBrowser 配置文件自动将所有标准头与配置文件的身份对齐。User-Agent、所有 Sec-CH-UA-* 头和 Accept-Language 都来源于配置文件的浏览器、平台和地区设置。通过 --bot-custom-headers 添加的自定义头扩展了这个基线,而不会干扰配置文件的标准头集。

技术背景

浏览器如何构造请求头

当浏览器发送 HTTP 请求时,它从多个来源构造头:

  1. 内置头User-AgentAcceptAccept-EncodingAccept-Language 由浏览器引擎根据内部设置添加。
  2. Client HintsSec-CH-UASec-CH-UA-MobileSec-CH-UA-Platform 默认发送。高熵提示如 Sec-CH-UA-Full-Version-List 仅在服务器通过 Accept-CH 请求时才发送。
  3. 应用头:通过 JavaScript 的 fetch()XMLHttpRequest 设置的头。
  4. 扩展注入的头:浏览器扩展通过 webRequest API 添加的头。

这些头的顺序和组成为每个浏览器创建了一致的模式。Chrome、Edge 和 Firefox 各自产生不同的头排序和组合。

基于配置文件的头一致性

当 BotBrowser 加载指纹配置文件时,它配置所有标准头以匹配配置文件的身份:

  • User-Agent 匹配配置文件的浏览器版本和平台
  • Sec-CH-UA 包含正确顺序的品牌令牌
  • Sec-CH-UA-Platform 匹配配置文件的操作系统
  • Accept-Language 反映配置的地区和语言偏好

这些头在引擎级别设置,适用于所有请求,包括初始导航请求、子资源加载和 JavaScript 发起的请求。不存在可能观察到错误头的时序间隙。

自定义头与标准头

自定义头(以 X- 开头或应用特定名称的头)与标准头有不同的用途。它们携带应用特定的数据:身份验证令牌、会话标识符、应用版本标记或路由提示。BotBrowser 的 --bot-custom-headers 标志添加这些自定义头而不修改配置文件管理的标准头。

Header Sources in BotBrowser Profile (Automatic) User-Agent, Sec-CH-UA-* Accept-Language CLI Override --bot-config-locale --bot-config-languages Custom Headers --bot-custom-headers BotBrowser.setCustomHeaders Final Request Headers All sources merge at the engine level before the request is sent

常见方案及其局限性

基于扩展的头修改

浏览器扩展可以通过 webRequest.onBeforeSendHeaders API 修改头。这种方案有几个缺点:

  • 时序问题:第一个请求(导航请求)可能在扩展的监听器完全注册之前触发
  • 覆盖不完整:Service Worker 请求、CORS 预检请求和一些内部请求可能不会触发扩展的监听器
  • 可检测的痕迹:修改头的扩展在浏览器的扩展 API 表面留下痕迹
  • 性能开销:每个请求都通过扩展的 JavaScript 处理程序,增加了延迟

基于 CDP 的头覆盖

Puppeteer 和 Playwright 通过 CDP 提供头覆盖 API:

// Puppeteer - 有限的方案
await page.setExtraHTTPHeaders({ 'X-Custom': 'value' });

这对页面级头有效,但有局限性:

  • 这种方式设置的头仅适用于特定页面
  • Service Worker 和共享 Worker 可能不会收到自定义头
  • CDP 的 Network.setExtraHTTPHeaders 命令需要 Network.enable,这本身可能影响指纹追踪

JavaScript Fetch/XHR 头

在单个 fetch()XMLHttpRequest 调用上设置头只覆盖 JavaScript 发起的请求。导航请求、图片加载、样式表获取和其他浏览器发起的请求不被覆盖。

BotBrowser 的方案

--bot-custom-headers 标志

BotBrowser 的 --bot-custom-headers 标志(PRO)在浏览器引擎级别注入自定义头:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-custom-headers='{"X-Custom-Header":"value","X-Auth-Token":"abc123"}' \
       --user-data-dir="$(mktemp -d)"

由于修改发生在 Chromium 网络栈内部,自定义头被添加到:

  • 初始导航请求
  • 子资源请求(图片、脚本、样式表、字体)
  • XHR 和 Fetch API 请求
  • WebSocket 升级请求
  • Service Worker 请求

没有时序间隙,没有遗漏的请求类型。

BotBrowser.setCustomHeaders CDP 命令

对于运行时头管理,BotBrowser 提供了一个可以发送到浏览器级会话的 CDP 命令:

Puppeteer:

const cdpSession = await browser.target().createCDPSession();
await cdpSession.send('BotBrowser.setCustomHeaders', {
  headers: { 'x-requested-with': 'com.example.app' }
});

Playwright:

const cdpSession = await browser.newBrowserCDPSession();
await cdpSession.send('BotBrowser.setCustomHeaders', {
  headers: { 'x-requested-with': 'com.example.app' }
});

重要提示:此命令必须发送到浏览器级 CDP 会话,而非页面级会话。发送到页面目标会返回 ProtocolError: 'BotBrowser.setCustomHeaders' wasn't found

配置文件级别配置

自定义头也可以在配置文件的 configs.customHeaders 中设置。当你希望头嵌入到配置文件本身而非在启动时指定时,这很有用。

配置和用法

使用自定义头的 CLI 设置

chrome --bot-profile="/path/to/profile.enc" \
       --bot-custom-headers='{"X-App-Version":"2.1.0","X-Session-ID":"sess_abc123"}' \
       --proxy-server=socks5://user:pass@proxy:1080 \
       --user-data-dir="$(mktemp -d)"

Playwright 集成

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
      '--bot-custom-headers={"X-Auth":"token123","X-Client":"webapp"}',
    ],
    headless: true,
  });

  const context = await browser.newContext();
  const page = await context.newPage();
  await page.goto('https://example.com');
  await browser.close();
})();

Puppeteer 集成

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
      '--bot-custom-headers={"X-Auth":"token123"}',
    ],
    headless: true,
    defaultViewport: null,
  });

  const page = await browser.newPage();
  await page.goto('https://example.com');
  await browser.close();
})();

JavaScript 引号处理

从 JavaScript 传递 --bot-custom-headers 时,不要在值内包含 shell 风格的引号:

// 正确
const headers = { 'X-Custom': 'value', 'X-Auth': 'token' };
args.push('--bot-custom-headers=' + JSON.stringify(headers));

// 错误 - 单引号会成为 JSON 值的一部分
args.push(`--bot-custom-headers='${JSON.stringify(headers)}'`);

通过 CDP 运行时更新头

对于需要在会话期间更改自定义头的工作流:

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
    ],
    headless: true,
  });

  const cdpSession = await browser.newBrowserCDPSession();

  // 设置初始头
  await cdpSession.send('BotBrowser.setCustomHeaders', {
    headers: { 'x-requested-with': 'com.example.app' }
  });

  const page = await (await browser.newContext()).newPage();
  await page.goto('https://example.com');

  // 会话中途更新头
  await cdpSession.send('BotBrowser.setCustomHeaders', {
    headers: { 'x-requested-with': 'com.example.app', 'x-session': 'refreshed' }
  });

  await page.goto('https://example.com/dashboard');
  await browser.close();
})();

验证

启动带有自定义头的 BotBrowser 后,验证它们是否已应用:

  1. 打开一个会回显请求头的页面(如 httpbin.org/headers)
  2. 确认你的自定义头出现在响应中
  3. 检查 DevTools 中的 Network 面板,查看所有请求类型上的头
  4. 验证标准头(User-Agent、Sec-CH-UA)仍与配置文件匹配
const page = await context.newPage();
await page.goto('https://httpbin.org/headers');
const headersText = await page.textContent('body');
console.log('Request headers:', headersText);

最佳实践

  1. 静态头使用 --bot-custom-headers。 如果头在会话期间不变,CLI 标志比 CDP 更简单。

  2. 动态头使用 BotBrowser.setCustomHeaders。 当头需要在会话期间更改(例如轮换认证令牌)时,使用 CDP 命令。

  3. 不要覆盖配置文件管理的头。 自定义头应添加新头,而不替换 User-Agent 或 Sec-CH-UA。覆盖配置文件头会造成不一致。

  4. 避免使用常见的扩展头名称。X-Forwarded-ForX-Real-IP 等头可能与代理基础设施冲突。使用应用特定的名称。

  5. 使用 httpbin.org/headers 进行测试。 此端点会回显所有收到的头,便于验证你的配置。

  6. setCustomHeaders 始终使用浏览器级 CDP 会话。 页面级会话会返回错误,因为该命令在浏览器级别运行。

常见问题

自定义头是应用于所有请求还是仅导航请求? 所有请求。BotBrowser 在引擎级别应用自定义头,覆盖导航、子资源、XHR、Fetch、WebSocket 和 Service Worker 请求。

我可以为每个上下文设置不同的自定义头吗? --bot-custom-headers 标志应用于所有上下文。对于每上下文的头,使用 BotBrowser.setCustomHeaders CDP 命令,可以在上下文操作之间更新。

自定义头是否影响指纹? 不属于标准浏览器头集的自定义头(如 X-Custom-Header)不影响浏览器指纹。它们只是请求中的附加头。

我可以删除标准头吗? BotBrowser 不支持通过自定义头删除标准头。配置文件管理的头始终包含,以维护指纹一致性。

--bot-custom-headers 是否与 --proxy-server 配合使用? 是的。无论是否配置代理,自定义头都会添加到请求中。代理看到完整的头集。

我可以设置的自定义头数量有上限吗? 没有特定限制。然而,过大的头集会增加请求大小,可能触发服务端限制。

我可以用 --bot-custom-headers 设置 Cookie 头吗? 对于 Cookie 管理,请使用专用的 --bot-cookies 标志。它提供带有域名作用域和过期时间的正确 Cookie 处理。

总结

HTTP 头控制对于维护一致的浏览器身份至关重要。BotBrowser 通过配置文件(标准头)、--bot-custom-headers 标志(静态自定义头)和 BotBrowser.setCustomHeaders CDP 命令(动态运行时头)提供引擎级头管理。这些工具结合使用,确保每个请求都携带一致、完整的头信息。

相关网络配置请参阅代理配置User-Agent 控制和 Client Hints。多账户身份管理请参阅多账户浏览器隔离

#HTTP 请求头#Custom-Headers#Network#Privacy#Request

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。