Puppeteer代理设置:标志、认证、轮换与现实

启动标志很简单;随后的407是大多数Puppeteer代理设置失败的地方。这里是完整的模式——page.authenticate、上下文轮换、proxy-chain——以及关于每页代理和无头检测的真相。

Puppeteer代理设置从一个启动标志开始,对于大多数人来说,下一步就遇到了障碍:代理返回407 Proxy Authentication Required,因为--proxy-server无法携带凭据。解决方法是内置的——page.authenticate()——但周围充满了半有效的建议:npm包悄悄地通过Node.js重路由你的流量,Chromium无法认证的SOCKS方案,以及使工作代理看起来失效的localhost绕过。本指南介绍了在生产环境中可靠的设置:标志、认证、浏览器上下文轮换、proxy-chain用于棘手的情况,以及关于无头检测的诚实部分。

Puppeteer代理设置:基础模式

代理是一个Chromium启动参数,因此适用于整个浏览器。凭据通过page.authenticate()传递,它通过DevTools协议响应代理的407挑战——在任何导航之前调用:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    args: ['--proxy-server=http://gate.quantumproxies.io:PORT'],
  });
  const page = await browser.newPage();
  await page.authenticate({ username: 'USER', password: 'PASS' });

  await page.goto('https://httpbin.org/ip', { waitUntil: 'domcontentloaded' });
  console.log(await page.evaluate(() => document.body.innerText)); // exit IP
  await browser.close();
})();

四个细节可以节省调试时间:

Puppeteer代理认证流程图:启动标志,page.authenticate注册凭据,代理407挑战响应,页面从住宅出口IP加载
page.authenticate通过DevTools协议注册凭据;当网关发送407时,Chromium在不显示对话框的情况下响应。

通过浏览器上下文轮换代理

每个IP重新启动Chrome需要几秒钟和数百MB的内存。浏览器上下文解决了这个问题:自Puppeteer v22起,API是browser.createBrowserContext()(取代了旧的隐身变体),它接受proxyServer选项,并在毫秒内创建一个具有自己cookie和存储的上下文:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const jobs = ['https://example.com/a', 'https://example.com/b'];

  for (const url of jobs) {
    const context = await browser.createBrowserContext({
      proxyServer: 'http://gate.quantumproxies.io:PORT',
    });
    const page = await context.newPage();
    await page.authenticate({ username: 'USER', password: 'PASS' });
    try {
      await page.goto(url, { timeout: 30000 });
      // ...extract...
    } finally {
      await context.close();
    }
  }
  await browser.close();
})();

对于旋转网关,每个上下文自然从池中的不同地址退出——使用旋转住宅代理,这意味着在一个主机名后面有90M+ IP跨越200+国家,并且当多页流程需要连续性时,粘性会话用户名参数可以固定一个出口。这是我们为Playwright推荐的相同的每个作业上下文架构;Puppeteer只是明确地拼写了认证步骤。

两个习惯保持轮换的诚实。在开发过程中记录每个上下文的出口IP——在上下文开始时命中一个IP回显端点,并将其与抓取的行一起存储,这样当目标开始软封锁时,你可以判断是一个出口还是一个指纹是罪魁祸首。并且限制你的并发:每个上下文都很便宜,但每个打开的页面仍然占用渲染器内存,因此在上下文创建周围使用信号量比在作业列表增长到数千个URL时使用无限循环更好。

proxy-chain:用于棘手认证的本地桥

两个情况打破了标志加认证模式:认证的SOCKS5(Chromium根本没有SOCKS凭据支持)和仅接受不带认证步骤的裸代理URL的工具。proxy-chain npm包通过启动一个本地的、无凭据的代理来解决这两种情况,该代理转发到你的认证上游:

const puppeteer = require('puppeteer');
const proxyChain = require('proxy-chain');

(async () => {
  const upstream = 'http://USER:PASS@gate.quantumproxies.io:PORT';
  const localUrl = await proxyChain.anonymizeProxy(upstream);
  // localUrl is something like http://127.0.0.1:54321 — no credentials needed

  const browser = await puppeteer.launch({
    args: ['--proxy-server=' + localUrl],
  });
  const page = await browser.newPage();
  await page.goto('https://httpbin.org/ip');

  await browser.close();
  await proxyChain.closeAnonymizedProxy(localUrl, true);
})();

关键是,页面的请求仍然从Chrome本身发出——proxy-chain只转发字节,因此你的TLS指纹保持为真实浏览器的。这一区别是下一节的重点。

每页代理:诚实的答案

Puppeteer没有原生的每页代理,而流行的解决方案——puppeteer-page-proxypuppeteer-proxy——拦截每个请求并从Node.js重新发出,然后将响应反馈给浏览器。三个后果:目标现在看到的是Node TLS握手而不是Chrome的,这在受保护的网站上会被JA3/JA4指纹识别立即标记;每个请求都需要通过Node的往返;这两个包实际上都没有维护,安装时就已经在其自身的问题跟踪器中被破坏。如果你需要为不同页面使用不同的IP,请像上面那样为每个代理使用一个上下文——效果相同,真实的Chrome流量,受支持的API。

Puppeteer代理轮换方法的比较:重新启动浏览器、带有proxyServer的浏览器上下文以及破坏TLS指纹的Node重路由包
上下文为你提供了真正的Chrome流量轮换。Node重路由包将你的TLS指纹换成机器人的——这与代理的目的相反。

无头检测的现实

代理修复了网络层;它无法使无头Chrome隐形。旧的无头模式通过HeadlessChrome User-Agent标记自己;新的无头模式(Puppeteer自v22以来的默认设置)共享真实浏览器的架构,缩小了这一差距,但检测器仍然会探测navigator.webdriver、CDP副作用和渲染怪癖——而隐身插件修补的是昨天的检查,而不是明天的。按努力回报顺序安排你的修复:首先是干净的住宅IP,因为声誉是网站运行的最便宜的过滤器,也是你的代码无法伪造的;其次是合理的头信息和节奏(我们的避免CAPTCHA指南涵盖了触发信号);当一个强化的目标仍然胜出时,通过一个Scraper API路由该域名,该API处理渲染、指纹和重试,并返回干净的HTML、markdown或JSON——一个HTTP调用而不是一群修补过的浏览器。

常见问题

如何在Puppeteer中认证代理?

在启动时使用args: ['--proxy-server=http://host:port']设置地址,然后在每个页面导航之前调用await page.authenticate({ username, password })。嵌入在标志URL中的凭据会被Chromium忽略。对于带认证的SOCKS5,使用proxy-chain作为桥梁,因为Chromium无法发送SOCKS凭据。

Puppeteer可以为每个页面使用不同的代理吗?

不是原生支持——启动标志是浏览器范围的。支持的等效方法是通过browser.createBrowserContext({ proxyServer })为每个代理创建一个浏览器上下文,并在每个上下文中放置页面。承诺真正每页代理的包会通过Node.js重路由请求,改变你的TLS指纹,并被严肃的反机器人系统标记。

Puppeteer支持SOCKS5代理吗?

是的,对于未经认证的端点:传递--proxy-server=socks5://host:port。认证的SOCKS5会失败,因为Chromium没有SOCKS凭据机制,而page.authenticate()只响应HTTP 407挑战。解决方法:使用网关的HTTP端口进行认证,将你的IP列入白名单,或运行proxy-chain作为本地桥。

如何在Puppeteer中轮换代理?

为每个作业创建一个新的浏览器上下文,使用createBrowserContext({ proxyServer })并指向一个旋转网关——每个上下文然后自动从一个新的IP退出,无需管理代理列表。每个IP重新启动整个浏览器也可以,但每次轮换需要几秒钟和数百MB的RAM。

为什么我的Puppeteer代理不适用于localhost?

Chromium设计上绕过环回地址的代理,因此对localhost或127.0.0.1的请求直接进行——这种行为让足够多的人感到惊讶,以至于成为一个编号的Puppeteer问题。添加--proxy-bypass-list=<-loopback>以强制代理,或简单地验证你的代理对外部IP回显端点。

持久的模式很小:地址的标志,page.authenticate()用于凭据,上下文用于轮换,proxy-chain用于棘手的情况——以及对任何将你的请求移出浏览器的包的怀疑。给这个堆栈干净的住宅出口,Puppeteer就会保持无趣,这是基础设施能获得的最高赞美。

在住宅代理上运行Puppeteer