Puppeteer 控制的是 Chrome/Chromium 浏览器请求。整个浏览器统一使用代理时,最常见的入口是 --proxy-server;只想让某个 BrowserContext 使用独立代理时,可以配置 proxyServer;HTTP 代理要求账号密码时,再通过 page.authenticate() 提供凭证。本文只解决代理配置、认证、出口 IP 验证和连接失败排查,不展开代理池或轮换调度。

需求推荐方式主要检查项
整个浏览器统一走代理--proxy-server代理协议、Host、Port
某个 BrowserContext 单独走代理proxyServer代理是否配置在正确 Context
部分 Host 不走代理--proxy-bypass-list / proxyBypassList绕过规则与目标 Host
HTTP 代理需要账号密码page.authenticate()凭证、认证方式与服务商规则
判断代理是否真正生效Direct vs Proxy 对比公网出口 IP 与地区

下文按本文发布时的 Puppeteer 25.11 官方文档核对。示例使用 Node.js ESM;先执行 npm install puppeteer,再保存为 .mjs 文件运行。Puppeteer 和 Chrome 的接口可能随版本调整,发布或升级项目时应再核对官方文档。

一、Puppeteer 代理有哪些配置方式?

整个浏览器:--proxy-server

要让这次启动的 Chrome/Chromium 统一经过同一个代理,可以通过 Puppeteer 的 args 把 --proxy-server 传给浏览器进程:

const browser = await puppeteer.launch({
  args: ['--proxy-server=http://proxy.example.com:7778']
});

args 是额外浏览器启动参数的入口。代理配置在浏览器启动时确定,不是页面打开以后再给某个请求临时增加的普通请求头。完整的启动选项可参考 Puppeteer LaunchOptions 文档。

某个 BrowserContext:proxyServer

同一个浏览器进程里,如果不同隔离上下文需要不同代理,可在创建 BrowserContext 时设置 proxyServer:

const context = await browser.createBrowserContext({
  proxyServer: 'http://proxy.example.com:7778'
});

当前 Puppeteer BrowserContextOptions 同时列出了 proxyServer 和 proxyBypassList。这个入口适合隔离 Context 的代理配置,但不等于 Puppeteer 提供了一个“任意 Page 随时更换代理”的通用 API。

浏览器代理不会自动接管 Node.js 请求

Puppeteer 的代理设置影响 Chrome/Chromium 页面请求,不会自动给同一脚本里的 Axios、Node.js fetch 或其他 HTTP Client 配置代理。page.goto() 和 Node.js 客户端请求属于不同网络路径,排查出口时必须分别验证。

Puppeteer代理配置方式:浏览器级代理、BrowserContext、bypass和认证
Puppeteer 代理配置入口:整个浏览器使用 --proxy-server,独立 Context 使用 proxyServer;绕过规则与 HTTP 代理认证分别配置。

二、怎么用 --proxy-server 配置 HTTP / HTTPS 代理?

最小可运行示例

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  headless: true,
  args: [
    '--proxy-server=http://proxy.example.com:7778'
  ]
});

try {
  const page = await browser.newPage();
  const response = await page.goto('https://example.com', {
    waitUntil: 'domcontentloaded',
    timeout: 30000
  });
  console.log('status:', response?.status());
} finally {
  await browser.close();
}

把示例 Host 和 Port 换成代理服务商提供的实际端点。代码能运行且页面能打开,只能证明请求完成,不能证明浏览器已经使用预期出口;第七节还会用公网 IP 做 A/B 验证。

代理地址里的 protocol、host、port 分别是什么?

字段示例容易误解的地方
protocolhttp描述客户端到代理端点的协议,不是目标网站协议
hostproxy.example.com不要把完整账密 URL 当成 Host
port7778以服务商提供的端口为准,不要凭协议名称猜端口

HTTPS 页面必须使用 HTTPS 代理吗?

不必须。--proxy-server=http://proxy.example.com:7778 可以用于访问 https://example.com。目标页面使用 HTTPS,不代表代理端点本身就是 HTTPS Proxy;只有代理服务商明确提供受 TLS 保护的代理端点时,才应按其说明使用 https://。

三、哪些地址不想走代理?怎么配置 bypass

浏览器级代理可以同时配置 bypass list,让匹配的目标绕过代理。命令行规则可写成以分号或逗号分隔的列表:

const browser = await puppeteer.launch({
  args: [
    '--proxy-server=http://proxy.example.com:7778',
    '--proxy-bypass-list=*.internal.example.com;api.internal.example.com'
  ]
});

使用 BrowserContext 代理时,对应字段是字符串数组:

const context = await browser.createBrowserContext({
  proxyServer: 'http://proxy.example.com:7778',
  proxyBypassList: [
    '*.internal.example.com',
    'api.internal.example.com'
  ]
});

Chromium 的 代理配置文档说明,bypass rules 决定哪些 URL 在已有代理配置时仍直接连接。bypass 是目标绕过规则,不是代理认证白名单,也不是安全访问控制。另外,Chrome 对 localhost 和部分链路本地地址存在隐式绕过规则,验证代理时不要默认用 localhost 作为目标。

四、一个浏览器里不同 Context 怎么使用不同代理?

BrowserContext 是同一浏览器进程中的隔离用户上下文。Cookies、LocalStorage 等会在 Context 之间隔离;当前版本还允许在创建 Context 时分别指定代理:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });

try {
  const usContext = await browser.createBrowserContext({
    proxyServer: 'http://proxy-us.example.com:7778'
  });
  const jpContext = await browser.createBrowserContext({
    proxyServer: 'http://proxy-jp.example.com:7778'
  });

  const usPage = await usContext.newPage();
  const jpPage = await jpContext.newPage();

  await Promise.all([
    usPage.goto('https://api.ipify.org?format=json'),
    jpPage.goto('https://api.ipify.org?format=json')
  ]);
} finally {
  await browser.close();
}
Puppeteer BrowserContext独立代理架构,不同Context分别使用不同代理
同一 Puppeteer Browser 可以创建多个隔离的 BrowserContext;每个 Context 绑定自己的代理,并由其中的 Page 发出请求。

这段代码只展示 Context 与代理的对应关系。实际项目还要分别处理认证、超时和出口验证。如果该 Context 使用的是需要用户名和密码认证的 HTTP 代理,仍应在对应 Page 首次导航前调用 page.authenticate();proxyServer 只负责指定代理端点。不要把“Context 可配置独立代理”理解成“同一个 Page 可以在请求进行中随意切换代理”;代理选择、会话隔离和页面生命周期仍应一起设计。

五、Puppeteer 代理账号密码怎么填写?

不要把账号密码直接塞进 --proxy-server

不要把下面这种地址当成 Chrome 手动代理认证的标准写法:

http://username:password@proxy.example.com:7778

Chromium 官方说明,Chrome 不会使用嵌在手动代理设置中的明文凭证。代理服务器地址和认证凭证应分开处理,避免出现地址看似完整、浏览器却仍弹出认证或返回 407 的情况。

使用 page.authenticate() 提供凭证

const page = await browser.newPage();

await page.authenticate({
  username: process.env.PROXY_USER,
  password: process.env.PROXY_PASS
});

await page.goto('https://example.com', {
  waitUntil: 'domcontentloaded',
  timeout: 30000
});

page.authenticate() 的官方定义是为 HTTP authentication 提供凭证,并不是只用于代理认证的专用 API。Puppeteer Page.authenticate() 文档还提醒,启用认证会在后台开启 request interception,可能影响性能。凭证应在访问需要认证的页面前设置,并通过环境变量或 Secret 管理,不要把真实密码写死在源码或日志里。

账号密码已填,为什么仍然返回 407?

407 Proxy Authentication Required 表示请求到达了要求认证的代理,但当前请求没有提供可接受的代理凭证。优先核对 Username、Password 和代理认证方式;如果服务商还要求来源 IP 白名单,或把地区、Session 等参数编入 Username,再继续核对这些服务商侧规则。若仍无法定位,可继续查看 407 代理认证专项排查,这里不重复 URL 编码和认证头的完整故障树。

六、HTTP、HTTPS、SOCKS5 在 Puppeteer 里怎么选?

代理类型Puppeteer / Chrome 常见入口主要注意点
HTTP--proxy-server=http://proxy.example.com:7778最常见;也能用于访问 HTTPS 页面
HTTPS Proxy--proxy-server=https://proxy.example.com:7778仅在代理端点本身支持 TLS 时使用
SOCKS5--proxy-server=socks5://proxy.example.com:1080Chrome 会让 SOCKS5 代理解析目标域名,但不支持 SOCKS5 认证方法

HTTP 代理可以代理 HTTPS 页面;SOCKS5 认证也不能直接套用 HTTP 代理的 page.authenticate() 逻辑。Chromium 当前文档明确指出,Chrome 的 SOCKS5 代理会在代理侧解析目标域名,但不支持 SOCKS5 authentication methods。如果服务商提供的 SOCKS5 端点必须使用账号密码,不能只照抄上面的浏览器启动参数,应先确认浏览器兼容的接入方式或使用受控的中间转发方案。

如果还不确定 HTTP、HTTPS 与 SOCKS5 的连接差异,可以先看站内的 代理协议区别与选型指南。本文只讨论这些协议在 Puppeteer 里的配置入口。

七、怎么验证 Puppeteer 浏览器真的经过代理?

页面能打开,不等于代理已经生效。应在同一台运行 Node.js 的机器上,用同一个 IP 查询接口分别执行 Direct 与 Proxy,两次只改变代理配置。下面示例把代理凭证作为可选参数;如果当前使用纯 IP 白名单认证,可以不传 credentials。

import puppeteer from 'puppeteer';

async function readEgressIP({ proxyServer, credentials } = {}) {
  const browser = await puppeteer.launch({
    headless: true,
    args: proxyServer ? [`--proxy-server=${proxyServer}`] : []
  });

  try {
    const page = await browser.newPage();
    if (credentials) {
      await page.authenticate(credentials);
    }

    await page.goto('https://api.ipify.org?format=json', {
      waitUntil: 'domcontentloaded',
      timeout: 30000
    });

    const text = await page.$eval('body', el => el.innerText);
    return JSON.parse(text).ip;
  } finally {
    await browser.close();
  }
}

const direct = await readEgressIP();
const viaProxy = await readEgressIP({
  proxyServer: 'http://proxy.example.com:7778',
  credentials: {
    username: process.env.PROXY_USER,
    password: process.env.PROXY_PASS
  }
});

console.log({ direct, proxy: viaProxy });
Puppeteer直连与代理出口IP的A/B验证示意
Puppeteer 出口验证示意:使用同一运行环境与同一接口比较 Direct 和 Proxy。图中 IP 应使用文档示例地址,不能把模拟结果标成真实测试。

正常使用独立代理出口时,Proxy 返回的公网地址通常应与 Direct 不同。如果配置了指定国家或地区,再使用同一个 GEO 数据来源核对地理信息;“IP 发生变化”和“地区符合预期”是两项不同的验收。固定代理、Sticky Session 与轮换代理还应按服务商声明的规则重复请求,不要把“每次都换 IP”当成所有产品的正确结果。

如果需要长期保持浏览器出口与会话一致,可以根据实际任务考虑 固定IP出口方案;若还分不清本地 IP、公网 IP 和代理出口,可先参考 本地 IP 与公网出口的判断方法。这里仅说明验证逻辑,不展开代理池与轮换调度。

八、Puppeteer 代理连接失败怎么排查?

先记录 Chrome 错误码、目标 URL、实际使用的代理 Host/Port、Context 和出口 IP,再逐项排查。不要同时修改代理地址、认证、bypass 与超时,否则很难判断是哪一项恢复了连接。

错误或现象优先检查处理方向
ERR_PROXY_CONNECTION_FAILEDHost、Port、代理端点可达性先验证能否连接代理,不急着改页面代码
407代理凭证、认证方式及服务商侧白名单/用户名规则确认 page.authenticate() 的调用时机并转入 407 专项排查
page.goto() timeout代理链路、目标响应、等待条件与超时值区分是代理连接慢、目标站慢还是等待条件未满足
页面能打开但出口 IP 没变--proxy-server 是否真正传入用第七节的同接口 A/B 测试复核
Context 出口不对proxyServer 是否配置在实际创建 Page 的 Context记录 Page 所属 Context,不混用页面引用
部分站点没走代理bypass list 与 Chrome 隐式绕过规则核对目标 Host 的实际匹配结果
HTTPS / 证书错误目标证书、代理端点协议、CA / TLS 链路先判断错误发生在哪一跳,不长期关闭证书验证
Node 请求和页面出口不同Node HTTP Client 与 Chrome 网络栈分别配置并分别验证两条请求路径

为什么 Node.js 请求和 Puppeteer 页面请求出口不一样?

page.goto()、页面资源加载与页面里的 fetch 走 Chrome/Chromium 网络栈;脚本进程直接执行的 Axios 或 Node.js fetch 不会自动继承 --proxy-server。因此,同一段程序中可能同时出现浏览器出口和 Node.js 直连出口。判断时应分别请求同一个 IP 查询接口,而不是把其中一条路径的结果当成整个进程的出口。

为什么账号密码正确还是 407?

密码正确只是一个条件。来源公网 IP 未加入认证白名单、代理用户名参数缺失、账号状态异常、认证方式不匹配,或 page.authenticate() 在导航后才调用,都可能让请求继续返回 407。先用最小页面与单一代理端点复测,再回到业务代码排查。

九、Puppeteer 代理常见问题

1. --proxy-server 可以直接带用户名密码吗?

不建议,也不能把它当成 Chrome 手动代理认证的可靠方式。Chrome 不使用嵌在手动代理设置中的明文凭证。代理地址放进 --proxy-server,HTTP 认证凭证再通过 page.authenticate() 提供。

2. page.authenticate() 是专门的代理认证 API 吗?

不是。它用于提供 HTTP authentication 凭证,既不是只服务代理认证的专用接口,也不能替代所有代理协议的认证流程。启用后还会开启 request interception,可能带来性能影响。

3. Puppeteer 能不能一个 BrowserContext 使用一个代理?

当前版本可以在创建 BrowserContext 时设置 proxyServer 和 proxyBypassList。Page 必须从对应 Context 创建,认证与出口也应按每个 Context 分别验证。

4. Puppeteer SOCKS5 代理怎么设置?

可使用 --proxy-server=socks5://host:port。Chrome 会把目标域名解析交给 SOCKS5 代理,但当前不支持 SOCKS5 authentication methods;需要账号密码的 SOCKS5 端点不能直接套用 HTTP 代理认证示例。

5. 为什么页面打开成功,但出口 IP 没变化?

先确认启动参数是否实际传入运行中的浏览器,再检查 Page 是否来自配置了代理的正确 Context、目标是否命中 bypass,以及所谓 Direct 基线是否被系统级网络转发接管。最后用同一接口做 A/B 对照。

6. Puppeteer 的代理会影响 Node.js Axios 或 fetch 吗?

不会自动影响。Puppeteer 的浏览器代理处理 Chrome/Chromium 页面流量;Node.js 客户端请求需要单独配置代理,并单独检查公网出口。

最终验收至少确认四项:代理配置作用在预期的 Browser 或 Context、认证可以完成、浏览器公网出口符合预期、Node.js 请求与页面请求没有被误认为同一条链路。页面成功打开只是起点,不是代理配置已经正确生效的最终证据。