浏览器自动化框架都将代理作为启动选项,但经过身份验证的代理需要特殊处理,因为 Chromium 不接受代理 URL 中的 user:pass@:Playwright 将凭据作为单独字段(并且可以为每个浏览器上下文设置不同的代理),Puppeteer 需要 page.authenticate(),而 Selenium 需要扩展或本地转发器。 本指南为每种框架提供了可用的代码,然后介绍了比语法更重要的一点:位于住宅 IP 后面的无头浏览器仍然可以被识别,除非其其余部分的故事匹配。

一个决定一切的事实:Chromium 忽略 URL 中的代理凭据

命令行工具和 Python 库可以轻松接受 http://user:pass@host:port。Chromium 的 --proxy-server 标志只接受 host:port(可选协议);其中的凭据会被静默丢弃。当代理响应 407 时,浏览器会显示身份验证对话框——而自动化脚本不会监视它。因此,每个框架都有自己提供用户名和密码的方式,这就是大多数“代理在 curl 中有效,但在浏览器脚本中无效”问题的来源。Firefox 在某些路径上更宽容,但以下特定于框架的机制适用于所有引擎。

Playwright

Playwright 对代理提供了一流的支持,具有单独的凭据字段,并且——使其成为多身份工作最佳选择的功能——代理可以按浏览器上下文设置,因此一个浏览器进程可以托管多个隔离的会话,每个会话从不同的 IP 退出。

Python

from playwright.sync_api import sync_playwright

PROXY = {"server": "http://proxy.example.com:8080",
         "username": "user", "password": "pass"}

with sync_playwright() as p:
    # 选项 A:整个浏览器使用一个代理
    browser = p.chromium.launch(proxy=PROXY)
    page = browser.new_page()
    page.goto("https://api.ipify.org?format=json")
    print(page.text_content("body"))
    browser.close()

    # 选项 B:每个上下文使用不同的代理(一个浏览器,多个身份)
    browser = p.chromium.launch(proxy={"server": "per-context"})   # 占位符;真正的代理来自上下文
    ctx_de = browser.new_context(proxy={"server": "http://gate.provider.com:7777",
                                        "username": "customer-country-de-session-a1", "password": "pass"},
                                 locale="de-DE", timezone_id="Europe/Berlin")
    ctx_us = browser.new_context(proxy={"server": "http://gate.provider.com:7777",
                                        "username": "customer-country-us-session-b2", "password": "pass"},
                                 locale="en-US", timezone_id="America/New_York")
    for ctx in (ctx_de, ctx_us):
        pg = ctx.new_page(); pg.goto("https://api.ipify.org?format=json"); print(pg.text_content("body"))
    browser.close()

两个细节。在 Chromium 中,按上下文代理要求浏览器使用代理选项启动(任何值——文档化的占位符是 {"server": "per-context"});否则,上下文无法覆盖。另外请注意,与代理一起设置的 locale 和 timezone_id:德国 IP 搭配美国时区是反机器人系统权衡的矛盾,如反机器人系统如何工作所述。支持 SOCKS5 代理("server": "socks5://host:1080"),但 Chromium 不会向 SOCKS 代理发送凭据,因此请使用 IP 白名单身份验证。

Node.js

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

(async () => {
  const browser = await chromium.launch({ proxy: { server: 'per-context' } });
  const context = await browser.newContext({
    proxy: { server: 'http://gate.provider.com:7777',
             username: 'customer-country-de-session-a1', password: 'pass' },
    locale: 'de-DE', timezoneId: 'Europe/Berlin',
  });
  const page = await context.newPage();
  await page.goto('https://api.ipify.org?format=json');
  console.log(await page.textContent('body'));
  await browser.close();
})();

Playwright 的 bypass 字段("bypass": "localhost,*.internal")将主机排除在代理之外,launch_persistent_context() 也接受相同的 proxy 选项,如果你需要持久化配置文件目录。

Puppeteer

Puppeteer 将代理作为 Chromium 标志传递,并按页面进行身份验证:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    args: ['--proxy-server=http://proxy.example.com:8080'],
  });
  const page = await browser.newPage();
  await page.authenticate({ username: 'user', password: 'pass' });   // 为此页面响应 407
  await page.goto('https://api.ipify.org?format=json');
  console.log(await page.evaluate(() => document.body.innerText));
  await browser.close();
})();

page.authenticate() 必须在导航之前调用,并且必须在每个将使用代理的页面(标签页)上调用。代理是浏览器范围的——Puppeteer 没有像 Playwright 那样的按上下文代理设置。对于多个身份,你有三个选项:启动多个浏览器进程(每个代理一个;重量级但简单),使用指向本地切换代理的 --proxy-server,该代理根据某些标记路由,或者改用 Playwright。还有用:--proxy-bypass-list=<-loopback> 如果你也需要代理 localhost,以及 puppeteer-extra 及其代理插件,它处理身份验证并可以按页面轮换。

Selenium

Selenium 通过浏览器选项设置代理。未认证(IP 白名单)代理只需一行;认证代理则是棘手的情况。

Chrome,白名单认证

from selenium import webdriver

opts = webdriver.ChromeOptions()
opts.add_argument("--proxy-server=http://proxy.example.com:8080")
driver = webdriver.Chrome(options=opts)
driver.get("https://api.ipify.org?format=json")
print(driver.find_element("tag name", "body").text)
driver.quit()

Chrome,用户名/密码

Selenium 的 WebDriver 协议没有钩子来响应代理的 407。标准的变通方法:

  1. Selenium Wire(Python):一个 Selenium 扩展,通过自己的本地代理路由流量,并支持认证上游:
    from seleniumwire import webdriver   # pip install selenium-wire
    opts = {"proxy": {"http": "http://user:pass@proxy.example.com:8080",
                      "https": "http://user:pass@proxy.example.com:8080"}}
    driver = webdriver.Chrome(seleniumwire_options=opts)
    请注意,Selenium Wire 不再积极维护;它仍然适用于许多人的当前 Chrome,但将其视为临时解决方案。
  2. 生成的 Chrome 扩展,通过 chrome.proxy 设置代理,并用凭据响应 onAuthRequired——这是一个众所周知的模式;该扩展是一个 manifest 加上几行后台脚本,用 opts.add_extension(...) 加载。在旧版 Chrome 的无头模式下不起作用(那里不支持扩展);在当前 headless=new 中有效。
  3. 本地转发代理,位于 127.0.0.1,持有凭据并转发到上游;将 Chrome 指向它,无需认证。框架无关且健壮。
  4. 白名单认证,如果你的机器有静态 IP,则来自提供商——完全避开问题。

Firefox

opts = webdriver.FirefoxOptions()
opts.set_preference("network.proxy.type", 1)
opts.set_preference("network.proxy.http", "proxy.example.com");  opts.set_preference("network.proxy.http_port", 8080)
opts.set_preference("network.proxy.ssl",  "proxy.example.com");  opts.set_preference("network.proxy.ssl_port", 8080)
opts.set_preference("network.proxy.socks_remote_dns", True)     # 如果使用 SOCKS,则远程 DNS
driver = webdriver.Firefox(options=opts)

Firefox 仍然会为用户名/密码代理弹出认证对话框;本地转发器或白名单方法适用。

代理无法修复无头浏览器中的问题

真实浏览器可以让你通过 TLS 和 HTTP/2 指纹识别——握手确实是 Chrome 的(参见TLS 指纹识别)。但它本身并不能让你通过 JavaScript 层:

  • navigator.webdriver 在自动化下为 true;网站首先检查它。
  • 无头模式有渲染和 API 差异(缺少插件、特定的 WebGL 渲染器字符串、窗口尺寸),指纹脚本会测试这些。Chrome 较新的无头模式(headless=new)更接近有头模式,但不完全相同。
  • WebRTC 仍然可以暴露机器的真实 IP,除非禁用或伪造;代理不会触及它。测试它——如何测试代理。
  • 时区、语言环境和地理位置来自机器,除非你设置它们(Playwright:timezone_id、locale、geolocation);与代理国家不匹配是经典的暴露信号。
  • 行为:即时导航、无鼠标移动、每次运行时间相同。

隐身插件(puppeteer-extra-plugin-stealth、playwright-stealth)修补最明显的痕迹;反检测浏览器更进一步,为每个配置文件提供一致的指纹,并暴露调试端口,你的 Playwright/Puppeteer 脚本可以附加到该端口——这是账户工作的生产级答案。参见什么是反检测浏览器,以及为每个配置文件选择 IP 类型,我们的反检测代理排名。

选择用于代理工作的框架

PlaywrightPuppeteerSelenium
认证代理原生(username/password 字段)每页 page.authenticate()需要变通方法(Wire、扩展、本地转发器)
每个会话不同的代理按上下文,一个浏览器每个代理一个浏览器每个代理一个驱动
SOCKS5是(无凭据 → 白名单)通过标志(无凭据)通过选项/首选项
每个会话的语言环境/时区原生上下文选项模拟 API / 标志首选项 / CDP
最适合多身份抓取和测试以 Node 为中心的 Chrome 自动化遗留套件、跨浏览器网格

常见问题

如何在 Playwright 中使用认证代理?

将 proxy={"server": "http://host:port", "username": "…", "password": "…"} 传递给 launch(),或按上下文传递给 new_context()(先使用 proxy={"server": "per-context"} 启动)。

如何在 Puppeteer 中使用带认证的代理?

使用 args: ['--proxy-server=host:port'] 启动,并在每个页面的 page.goto() 之前调用 await page.authenticate({username, password})。

为什么 Chrome 忽略 --proxy-server 中的 user:pass@?

Chromium 的标志只接受 scheme://host:port;不解析凭据。它用交互式对话框响应代理的 407,因此自动化必须通过框架的机制提供凭据。

我可以为每个标签页使用不同的代理吗?

不能按标签页。Playwright 按浏览器上下文(每个上下文是隔离的会话);Puppeteer 和 Selenium 需要每个代理一个浏览器实例,或本地切换代理。

代理能让无头 Chrome 无法检测吗?

不能。它修复了 IP 层,并且作为真实浏览器,修复了 TLS 层。自动化标志、无头痕迹、WebRTC、时区不匹配和行为是单独的信号,需要单独处理。

对于浏览器自动化,代理应该是住宅还是数据中心?

取决于目标的 IP 策略——浏览器不会改变这个计算。在容忍的地方使用数据中心;在受保护的网站上使用住宅或移动;对于持久登录的配置文件使用静态(ISP)。参见代理类型比较。