知识

如何在 Puppeteer 中使用住宅代理(含 page.authenticate)

Puppeteer 里的代理:代理放进启动参数,凭据放进 page.authenticate,经一个 gateway 做按页面地理轮换,以及资源拦截。

Chris Collins

Chris Collins

2026年8月5日 · 2 分钟阅读

Puppeteer 驱动的是一个真正的 Chromium——当一个目标用 JavaScript 渲染内容、把数据藏在交互之后、或对任何不是真浏览器的东西做指纹时,这正是你想要的。接入一个住宅代理只是两行,但 Puppeteer 把这活儿拆到两个不同的地方,其方式几乎让每个人第一次都栽跟头:代理地址放进启动参数,而凭据则完全放到另一处。

这是与用 Playwright 使用住宅代理并列的 Puppeteer 篇;如果你想要的是朴素的 HTTP 客户端、而不是一个完整浏览器,见Node.js 里的代理。这里我们专注于浏览器特有的那些坑。

下面的一切都使用 Shifter 的住宅 gateway:一个端点 p.shifter.io:443,所有定位都编码在用户名里。换成别家供应商,就换掉主机和凭据;形态是一样的。

一段话讲清 gateway 模型

代理用户名同时承载你的认证你的定位。你不是靠切换端点来换国家或换会话,而是靠改用户名字符串:

customer-USERNAME-country-us-sid-abc123-ttl-600

country-us 定位美国,sid 固定一个粘性会话,ttl 把那个 IP 保持 N 秒。去掉 sid/ttl,每一条新连接都会轮换。密码是恒定的。在 Puppeteer 里,主机放进一个启动 flag,而那个用户名放进 page.authenticate

搭建:代理放启动参数,凭据放 page.authenticate

Chromium 把代理服务器当作一个命令行 flag——--proxy-server——经由 Puppeteer 的 args 传入。它不会在那里接受 user:pass@host,Chromium 不从那个 flag 读凭据。取而代之,你按页面用 page.authenticate 提供凭据,它会用正确的 Proxy-Authorization 回应代理的 407 挑战。

import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true,
args: ['--proxy-server=http://p.shifter.io:443'], // 只有主机,没有凭据
});
const page = await browser.newPage();
await page.authenticate({
username: `${process.env.SHIFTER_USER}-country-us`, // 定位就住在这里
password: process.env.SHIFTER_PASS,
});
await page.goto('https://api.ipify.org');
console.log(await page.evaluate(() => document.body.innerText)); // 一个美国住宅 IP
await browser.close();

有两件事要内化。用户名里包含了定位标志(-country-us),因为地理住在那里、而不是在 URL 里。而对一个需认证的代理,page.authenticate 不是可选项——没有它,每一次导航都死在一个 407 上。这个拆分——主机在 flag 里、凭据在 page.authenticate 里——正是最常见的 Puppeteer 代理错误。

经一个 gateway 轮换地理与会话

这就是那个设计带来的有用后果。代理主机在浏览器启动时被固定、你无法按页面改它,但你并不需要。因为定位住在用户名里、而 page.authenticate 是按页面设置的,给每个页面一个不同的用户名,就会把它经同一个 gateway 上的不同身份路由出去。一个浏览器、许多地理、无需重启。

async function pageFor(browser, country, sid) {
const page = await browser.newPage();
const user = `${process.env.SHIFTER_USER}-country-${country}`
+ (sid ? `-sid-${sid}-ttl-600` : '');
await page.authenticate({ username: user, password: process.env.SHIFTER_PASS });
return page;
}
const de = await pageFor(browser, 'de', 'job-42'); // 德国粘性会话
const us = await pageFor(browser, 'us', null); // 轮换的美国

给每个逻辑工作单元它自己的 sid,并在单元之间轮换、而不是在会话中途轮换(粘性 vs 轮换讲了这个区分),并按负载均衡那篇所述把工作映射到身份。要在身份之间隔离 cookie 和存储,就把每个身份放进它自己的浏览器上下文:

const context = await browser.createBrowserContext(); // 隔离的 cookie/存储
const page = await context.newPage();
await page.authenticate({ username: userFor('gb'), password: pass });

注意名字:较新的 Puppeteer 把它叫作 createBrowserContext(),旧版本叫它 createIncognitoBrowserContext()。同一个思路,改了名。

坑 1:复用浏览器,绝不每个请求启一个

启动 Chromium 很重——每个请求都开一个新鲜浏览器,每次都要付几百毫秒的启动加上真实内存,正是延迟指南存在的意义所要消除的那部分开销。在启动时开一个浏览器并复用它,为并发工作开页面或上下文、干完就关掉它们。一个长生命周期浏览器之下的页面池,是正确的形态。

坑 2:拦掉你不需要的资源

一个浏览器会像真浏览器那样把一切都抓下来:图片、字体、媒体、样式表、分析脚本。如果你只想要 HTML 或几个字段,那就是你在付的带宽、和你在等的时间。Puppeteer 让你拦截请求、并中止你不需要的那些,这会大幅削减你的页面加载时间和你的带宽账单

await page.setRequestInterception(true);
page.on('request', (req) => {
const blocked = ['image', 'font', 'media', 'stylesheet'];
if (blocked.includes(req.resourceType())) req.abort();
else req.continue();
});

要有选择:有些站点没有它们的 CSS 或某个特定脚本就渲染不出你想要的内容,所以先激进地拦,然后确认数据仍然出现。当它出现时,这是一个 headless 浏览器里能拿到的最便宜的提速。

坑 3:浏览器只是”不被封”的一半

一个住宅 IP 处理了”看起来像人”的网络那一半,但 Puppeteer 驱动的仍是 headless 的 Chromium,而站点也会对浏览器做指纹——navigator.webdriver、headless 特有的怪癖、以及自动化信号。一个有良好信誉的干净 IP 能让你避开很多挑战,但它并不能给一个明显自动化的浏览器打掩护。用一个当前版本的 Puppeteer 和 Chromium,好让你拿到现代的 headless 模式、而不是那个老旧、易被检测的模式;把视口和 user-agent 保持真实;并以人的节奏驱动页面。那些触发检测的错误对浏览器这一层和对 IP 这一层同样适用,而这两者必须对得上。

坑 4:给你的并发封顶

每个打开的页面都是一个占着真实内存的真实浏览器标签页,所以你不能像发 HTTP 请求那样开成千上万个。保持一个有界的页面或上下文池并复用它们,并按目标主机给在途工作封顶,好让脆弱的站点不被猛击、宽松的站点也不被饿死。越过一个目标的容忍度之后,更多并行买来的是封锁和内存溢出崩溃,而不是吞吐量(如何避免被封)。

验证你确实在走代理

在给别的任何东西做基准或调试之前,从页面内部确认出口 IP:

await page.goto('http://ip-api.com/json');
console.log(await page.evaluate(() => document.body.innerText)); // 期望是目标国家

返回你自己的 IP,说明 --proxy-server flag 没生效。卡在一个 407 对话框上,说明 page.authenticate 缺失、或凭据不对。整体挂住,说明本地出站被挡了。这三种都在超时诊断指南里有讲。

常见问题

为什么把 user:pass@host 放进 --proxy-server 不管用? Chromium 不从 --proxy-server flag 里读代理凭据。那里只传主机,并用 page.authenticate({ username, password }) 提供凭据,它会处理代理的 407 挑战。因为 gateway 把定位编码在用户名里,那个完整用户名(带 -country-...)要进 page.authenticate

如果代理是在启动时固定的,我怎么按页面用不同国家? 你不改代理主机,你改 page.authenticate 的用户名。因为定位住在用户名里、而 gateway 主机是恒定的,每个页面都能用不同的用户名认证、并经不同的身份出口。一个浏览器服务许多地理。

我能不重启浏览器就轮换代理吗? 能——对身份和地理而言,靠按页面或按上下文变换 page.authenticate 的用户名。你只有在需要一个真正不同的代理主机时才需要重启,而用一个单一的 gateway 端点你并不需要。

在 Puppeteer 里我怎么削减带宽? 开启请求拦截,并中止你不需要的资源类型(图片、字体、媒体,常常还有样式表)。确认目标仍然渲染出你想要的数据,然后保留这些拦截。它是一个 headless 浏览器里对速度和成本最大的那个单一杠杆。

Puppeteer 还是 Playwright? 两者都驱动真浏览器、也都能和住宅代理配合得很好。Playwright 在它的上下文选项里直接接收代理(连同凭据);Puppeteer 把它拆成启动 flag 加 page.authenticate。按生态契合度和你已有的代码来选;代理的概念是一样的。

底线

一旦那个拆分想通了,Puppeteer 加住宅代理就很直接:代理主机在启动时进 --proxy-server,而把你的定位承载在用户名里的凭据,按页面进 page.authenticate。靠变换那个用户名来轮换地理与会话、而不是重启,用浏览器上下文隔离身份,复用一个长生命周期的浏览器,拦掉你不需要的资源,并记住浏览器指纹必须和 IP 一样看起来像人。

把这些做对,Puppeteer 就能搞定那些朴素 HTTP 客户端搞不定的、重 JavaScript 的目标。把它指向住宅 gateway,并记住池的质量决定了你到底会不会频繁被挑战(IP 信誉)。定价页面有按 GB 计费的套餐,可以拿它对着你自己的目标试用。

准备好开始了吗?

试用 Shifter 住宅代理,205M+ 个 IP,195+ 个国家,低至 $0.75/GB。

立即开始