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 发出:
import nodriver as uc
async def main():
browser = await uc.start(
browser_args=["--proxy-server=gate.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 错误指南 中提到的墙:是代理拒绝了你,而不是浏览器。因此,每个真正的解决方案都必须以其他方式应对挑战。
解决方案 1:IP 白名单——无凭据,无对话框
这是 GitHub 线程中从未提到的快捷方式,也是最简单的。如果你的爬虫运行在具有稳定公共 IP 的机器上,请在你的提供商仪表板中注册该 IP,完全放弃凭据——网关通过源地址对你进行认证。nodriver 代码保持为上面的简单 --proxy-server 片段,没有任何认证代码。每个 QuantumProxies 计划都支持在其 住宅代理 上同时使用 IP 白名单和用户:密码,因此只要你的出口 IP 是固定的,这就是推荐的路径。它唯一的限制是拓扑:它认证的是机器,而不是脚本,因此需要使用接下来的两个解决方案之一来处理临时云运行器、NAT 后的容器和 IP 变化的 CI 盒子。

解决方案 2:使用 CDP Fetch 处理程序回答挑战
nodriver 直接使用 Chrome DevTools 协议,因此你可以在进程中拦截认证挑战——不需要扩展文件。启用 Fetch 域并设置 handle_auth_requests=True,然后对每个 AuthRequired 事件使用 continue_with_auth 进行响应。两个细节,均来自讨论 #1798 的答案,是工作与挂起的区别:
- 在启用域之前注册处理程序。 nodriver 的内部
enable调用会覆盖你的处理程序注册,因此如果你在之后添加它们,事件永远不会触发——这是人们报告“它什么也不做”的首要原因。 - 发送响应时不等待。 在处理程序中等待回复会阻塞事件循环并导致整个浏览器死锁。将每个发送包装在
asyncio.create_task中,以便它在不阻塞的情况下运行。
import asyncio
import nodriver as uc
PROXY = "gate.quantumproxies.io:PORT" # 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("draft:,") # 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 加载它:
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 代理认证指南 中的 Manifest V3 文件相同——逐字复制它们,只有启动调用发生变化。两个常见问题在各处重复:扩展仅在 --headless=new 下加载(普通 --headless 失败),并且未打包的目录在不同的 Chrome 版本中比打包的 zip 更可靠。扩展可以处理任何代理类型,这使它成为 CDP 路线与你作对时的后备。
第四个选项:本地中继
如果你不想碰 nodriver,可以运行一个小型本地中继,它保存凭据并在 127.0.0.1 上提供一个无认证的端点。然后 nodriver 指向环回地址,使用简单标志,从未看到挑战。这是 SOCKS5 的最干净路线,因为 Chromium 完全拒绝认证代理(跟踪为 Chromium bug 40829748)。我们在 代理中继指南 中介绍了最小中继、现成工具以及何时过度使用。
关于 SOCKS5 的说明
未经认证的 SOCKS5 可以通过标志工作——--proxy-server=socks5://host:port——但认证的 SOCKS5 不行,没有 CDP 处理程序可以拯救你,因为 Chromium 从未提供过 SOCKS5 用户名/密码支持。实际的答案是相同的三种:将 IP 列入白名单,运行一个添加凭据的中继,或使用提供商的 HTTP 端点代替。每个 QuantumProxies 计划都在同一网关上同时提供 HTTP 和 SOCKS5 代理,因此切换到 HTTP 端点通常是最快的 SOCKS5 解决方案。对于反检测浏览器家族,认证代理地图将 nodriver、zendriver 和其他进行并排比较。

常见问题解答
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 保留一个中继。上面的代码回答了挑战——但干净的住宅出口才是真正让你通过的关键。