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 盒子。

四种 nodriver 代理认证路线的比较:IP 白名单、CDP Fetch 认证处理程序、生成的 Chrome 扩展和本地中继
当你的 IP 是固定的时,白名单不需要任何代码;CDP 处理程序和扩展在不是固定时回答凭据挑战。

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

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

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 和其他进行并排比较。

流程显示如何在代理网关上进行 IP 白名单让 nodriver 脚本使用简单代理标志运行且无凭据对话框
当出口 IP 稳定时,白名单认证机器,认证代码完全消失。

常见问题解答

nodriver 支持认证代理吗?

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

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

几乎总是因为你在注册处理程序之前启用了 Fetch 域。nodriver 的内部 enable 会覆盖注册,因此事件永远不会到达你的回调。首先添加 RequestPausedAuthRequired 处理程序,然后调用 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