# Nodriver 代理认证：有效的三种解决方案

关于 nodriver 代理认证的搜索结果，排在首位的是 GitHub 讨论，而不是指南。nodriver 没有原生的用户：密码支持——以下是有效的三种解决方案，以及大多数人忽略的一行快捷方式。

搜索 **nodriver 代理认证**，前十个结果是一个 GitHub 讨论、一个演示库、几个关于不同库的 Stack Overflow 线程和一个 Reddit 帖子——没有实际的指南。原因很简单：nodriver 是 undetected-chromedriver 的异步 CDP 继任者（该项目在 GitHub 上有 12.8k 星和 1.3k 分叉），没有原生方式将 `user:pass` 传递给代理。Chrome 忽略了嵌入在命令行标志中的凭据，而 nodriver 没有对此进行掩饰。此指南是该讨论线程应有的页面：什么有效，什么无效，以及让认证代理运行的三种解决方案。

## 普通代理有效；认证代理无效

未经认证的代理是一行代码。通过 `browser_args` 传递地址，每个请求都从代理 IP 发出：

```python
import nodriver as uc

async def main():
    browser = await uc.start(
        browser_args=["--proxy-server=residentialboson.quantumproxies.io:PORT"],
    )
    page = await browser.get("https://httpbin.org/ip")
    print(await page.get_content())   # shows the proxy exit IP

uc.loop().run_until_complete(main())
```

现在添加凭据——`--proxy-server=http://USER:PASS@host:port`——然后它就会出错。Chromium 会剥离 `USER:PASS@` 段，因为标志格式没有凭据槽，然后代理会以 `407 Proxy Authentication Required` 回应，Chrome 会弹出一个原生登录对话框，该对话框存在于 DOM 之外。nodriver 无法看到或填写它。那个 407 就是我们在 [修复 407 错误指南](https://quantumproxies.io/blog/fix-407-proxy-authentication-required) 中提到的墙：是代理拒绝了你，而不是浏览器。因此，每个真正的解决方案都必须以其他方式应对挑战。

## 解决方案 1：IP 白名单——无凭据，无对话框

这是 GitHub 线程中从未提到的快捷方式，也是最简单的。如果你的爬虫运行在具有稳定公共 IP 的机器上，请在你的提供商仪表板中注册该 IP，完全放弃凭据——网关通过源地址对你进行认证。nodriver 代码保持为上面的简单 `--proxy-server` 片段，没有任何认证代码。每个 QuantumProxies 计划都支持在其 [住宅代理](https://quantumproxies.io/residential-proxies) 上同时使用 IP 白名单和用户：密码，因此只要你的出口 IP 是固定的，这就是推荐的路径。它唯一的限制是拓扑：它认证的是机器，而不是脚本，因此需要使用接下来的两个解决方案之一来处理临时云运行器、NAT 后的容器和 IP 变化的 CI 盒子。

![在网关上为 nodriver 代理做认证的四条路线：IP 白名单、CDP Fetch 认证处理程序、生成的 Chrome 扩展和本地中继。卡片说明了各自的适用场景：固定 IP 下白名单无需任何代码，IP 变化时由处理程序或扩展响应挑战，SOCKS5 则交给中继。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785776549/blog/generated/qp-b10-001-diag1.png)
*当你的 IP 是固定的时，白名单不需要任何代码；CDP 处理程序和扩展在不是固定时回答凭据挑战。*

## 解决方案 2：使用 CDP Fetch 处理程序回答挑战

nodriver 直接使用 Chrome DevTools 协议，因此你可以在进程中拦截认证挑战——不需要扩展文件。启用 `Fetch` 域并设置 `handle_auth_requests=True`，然后对每个 `AuthRequired` 事件使用 `continue_with_auth` 进行响应。两个细节，均来自讨论 #1798 的答案，是工作与挂起的区别：

- **在启用域之前注册处理程序。** nodriver 的内部 `enable` 调用会覆盖你的处理程序注册，因此如果你在之后添加它们，事件永远不会触发——这是人们报告“它什么也不做”的首要原因。
- **发送响应时不等待。** 在处理程序中等待回复会阻塞事件循环并导致整个浏览器死锁。将每个发送包装在 `asyncio.create_task` 中，以便它在不阻塞的情况下运行。

```python
import asyncio
import nodriver as uc

PROXY = "residentialpremium.quantumproxies.io:8080"   # host:port for --proxy-server
USER, PASS = "USER", "PASS"

class Scraper:
    def __init__(self):
        uc.loop().run_until_complete(self.run())

    async def run(self):
        browser = await uc.start(browser_args=[f"--proxy-server={PROXY}"])
        self.tab = await browser.get("data:,")        # blank tab first

        # 1) handlers BEFORE enabling the Fetch domain
        self.tab.add_handler(uc.cdp.fetch.RequestPaused, self.on_request)
        self.tab.add_handler(uc.cdp.fetch.AuthRequired, self.on_auth)
        # 2) only now turn on interception with auth handling
        await self.tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))

        page = await browser.get("https://httpbin.org/ip")
        await asyncio.sleep(3)
        print(await page.get_content())

    async def on_auth(self, event):
        # fire-and-forget: awaiting here deadlocks the loop
        asyncio.create_task(self.tab.send(uc.cdp.fetch.continue_with_auth(
            request_id=event.request_id,
            auth_challenge_response=uc.cdp.fetch.AuthChallengeResponse(
                response="ProvideCredentials", username=USER, password=PASS,
            ),
        )))

    async def on_request(self, event):
        asyncio.create_task(self.tab.send(
            uc.cdp.fetch.continue_request(request_id=event.request_id)))

if __name__ == "__main__":
    Scraper()
```

同一线程中出现的一个警告：一位用户发现这在普通 HTTP 页面上有效，但在 HTTPS 上失败，罪魁祸首是低质量的代理，而不是代码——换用更好的出口解决了问题。这是隐秘爬虫的反复教训：处理程序回答了挑战，但 IP 声誉决定了网站是否让你进入。

## 解决方案 3：生成的 Chrome 扩展

另一个社区模式是在启动时构建一个小型 Chrome 扩展，该扩展既设置代理又通过 `chrome.webRequest.onAuthRequired` 回答凭据挑战——这与 Selenium 和 Puppeteer 中的技巧相同。你在临时目录中写入一个小的清单和一个后台工作者，并通过 `--load-extension` 加载它：

```python
import nodriver as uc

async def main():
    # ext_dir holds a Manifest V3 extension: manifest.json + worker.js that
    # calls chrome.proxy.settings.set(...) and returns authCredentials from
    # chrome.webRequest.onAuthRequired. Generate it once, then load it:
    browser = await uc.start(browser_args=[
        "--load-extension=" + ext_dir,
        "--headless=new",   # extensions only load in the NEW headless mode
    ])
    page = await browser.get("https://httpbin.org/ip")
    print(await page.get_content())

uc.loop().run_until_complete(main())
```

完整的清单和工作者与我们在 [Selenium 代理认证指南](https://quantumproxies.io/blog/selenium-proxy-setup-authenticated) 中的 Manifest V3 文件相同——逐字复制它们，只有启动调用发生变化。两个常见问题在各处重复：扩展仅在 `--headless=new` 下加载（普通 `--headless` 失败），并且未打包的目录在不同的 Chrome 版本中比打包的 zip 更可靠。扩展可以处理任何代理类型，这使它成为 CDP 路线与你作对时的后备。

## 第四个选项：本地中继

如果你不想碰 nodriver，可以运行一个小型本地中继，它保存凭据并在 `127.0.0.1` 上提供一个无认证的端点。然后 nodriver 指向环回地址，使用简单标志，从未看到挑战。这是 SOCKS5 的最干净路线，因为 Chromium 完全拒绝认证代理（跟踪为 Chromium bug 40829748）。我们在 [代理中继指南](https://quantumproxies.io/blog/proxy-relay-socks5-auth-tools) 中介绍了最小中继、现成工具以及何时过度使用。

## 关于 SOCKS5 的说明

未经认证的 SOCKS5 可以通过标志工作——`--proxy-server=socks5://host:port`——但认证的 SOCKS5 不行，没有 CDP 处理程序可以拯救你，因为 Chromium 从未提供过 SOCKS5 用户名/密码支持。实际的答案是相同的三种：将 IP 列入白名单，运行一个添加凭据的中继，或使用提供商的 HTTP 端点代替。每个 QuantumProxies 计划都在同一网关上同时提供 HTTP 和 [SOCKS5 代理](https://quantumproxies.io/socks5-proxies)，因此切换到 HTTP 端点通常是最快的 SOCKS5 解决方案。对于反检测浏览器家族，[认证代理地图](https://quantumproxies.io/blog/anti-detect-frameworks-authenticated-proxies-map)将 nodriver、zendriver 和其他进行并排比较。

![nodriver 的 IP 白名单方案：在服务商处登记机器的出口 IP，用普通的 --proxy-server 参数启动，网关根据源地址进行认证，因此不会出现 407，也不会弹出凭据对话框。卡片显示认证代码为零，适合固定 IP 的 VPS，但不适合 NAT 后面的 CI 运行器。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785776549/blog/generated/qp-b10-001-diag2.png)
*当出口 IP 稳定时，白名单认证机器，认证代码完全消失。*

## 常见问题解答

### nodriver 支持认证代理吗？

不是原生支持。你可以通过 `browser_args=["--proxy-server=host:port"]` 传递未经认证的代理，但 Chromium 会剥离标志中的 `user:pass`。要进行认证，你可以在提供商处将你的 IP 列入白名单，使用 CDP `Fetch.AuthRequired` 处理程序回答挑战，生成一个代理认证 Chrome 扩展，或运行一个保存凭据的本地中继。

### 为什么我的 nodriver 认证处理程序没有收到事件？

几乎总是因为你在注册处理程序之前启用了 `Fetch` 域。nodriver 的内部 `enable` 会覆盖注册，因此事件永远不会到达你的回调。首先添加 `RequestPaused` 和 `AuthRequired` 处理程序，然后调用 `fetch.enable(handle_auth_requests=True)`。还要将你的响应包装在 `asyncio.create_task` 中，以便等待它们不会阻塞循环。

### nodriver 能否使用带有用户名和密码的 SOCKS5 代理？

不能。Chromium 不支持认证的 SOCKS5（Chromium bug 40829748），nodriver 继承了这个限制。未经认证的 SOCKS5 可以通过 `--proxy-server=socks5://host:port` 工作。对于认证的 SOCKS5，将你的 IP 列入白名单，运行一个添加凭据的本地中继，或切换到提供商的 HTTP 端点，它可以干净地处理基本认证。

### nodriver 还是 zendriver 用于认证代理？

两者共享相同的缺口和相同的解决方案，因为 zendriver 是 nodriver 的社区分叉。zendriver 有一个更活跃的问题跟踪器，认证问题在此公开讨论，但工作方法是相同的。如果你在分叉上，分叉特定的设置镜像了这里的一切——CDP 处理程序和白名单的行为相同。

诚实的总结：nodriver 不会为你进行代理认证，一旦你知道地图，这就没问题。IP 稳定时使用白名单，IP 不稳定时选择 CDP 处理程序或扩展，并为 SOCKS5 保留一个中继。上面的代码回答了挑战——但干净的住宅出口才是真正让你通过的关键。

[在白名单的住宅 IP 上运行 nodriver](https://quantumproxies.io/residential-proxies)
