如何在异步 Python 中用 CapSkip 识别 Pydoll 验证码

pydoll captcha - How to Solve a Pydoll CAPTCHA With CapSkip in Async Python

要识别 Pydoll 验证码,先从页面读出 sitekey,再在同一个 asyncio 事件循环上用 CapSkip 的 AsyncCapSkip 客户端识别,把 token 写进 g-recaptcha-response 输入框,然后运行页面的回调或提交表单。Pydoll 3.0 自己没有任何 reCAPTCHA 代码,所以 token 必须来自识别工具。CapSkip 运行在你自己的 Windows PC 上,因此要打开很多页面的爬取任务没有按次识别的费用。本指南用大约 80 行代码搭起完整的 Pydoll 验证码流程,并讲到在 Windows 上最费时间的那些陷阱。

你需要什么

  • 在 Windows PC 上运行的 CapSkip,并已开启 reCAPTCHA 识别功能。 设置指南 会带你完成首次启动的每一步。
  • Python 3.10 或更高版本。Pydoll 3.0.0 和 capskip 1.3.0 包都要求它。下面的所有内容都在 Windows 11 上用 Python 3.14.0 运行过,完整脚本也在 Python 3.11.7 上运行过。Python 3.10、3.12 和 3.13 没有运行过。
  • Google Chrome,安装在默认的 Program Files 文件夹里。本指南使用的是 Chrome 155,完整脚本也在 Chrome for Testing 146 上运行过。
  • 一个 PowerShell 提示符。这些命令使用 PowerShell 语法。
  • 一个你自己拥有的页面,带有 reCAPTCHA v2 复选框。示例表单是一个普通的注册表单。请把选择器改成与你的页面一致。
  • 识别工具的地址。在 Local 模式下,CapSkip 只在 127.0.0.1 上响应,仅供本机使用。在 Server 模式下,它监听你的网络地址或公网 IP,这样另一台 PC、VPS 或托管平台上的 Pydoll 就能通过 API 调用它。两种模式的设置位置都是 连接设置.

下面的测量数据来自同一台 PC 上的一个模拟 CapSkip 和一个模拟注册页面,配合真实的 Pydoll、真实的 SDK、真实的 Chrome 和 Google 真实的 api.js 脚本使用。浏览器和 SDK 的行为在你自己的站点上同样成立。识别耗时,以及页面的接受和拒绝规则,则只属于这两个模拟程序本身。对一个运行中的 CapSkip 做的一次识别,使用 Google 的公开测试 sitekey 和默认设置,用了 9.0 秒,返回了一个 1,764 个字符的 token。识别耗时每次运行都不一样。这个 token 没有用在任何地方,也没有在任何线上站点上尝试过。

Pydoll 到哪里为止,CapSkip 从哪里开始

Pydoll 通过 DevTools 协议驱动 Chrome,中间没有 WebDriver。它给你浏览器、页面元素,以及一种运行 JavaScript 的方式。在 3.0.0 里,它唯一的验证码代码是一个针对 Cloudflare Turnstile 复选框的辅助函数。这个辅助函数找到复选框并点击它。它从不读取 token,什么都没发生时也从不抛出错误。reCAPTCHA 则完全没有处理。reCAPTCHA v2 复选框要由一个返回 token 的服务来识别,之后页面还需要在正确的位置拿到这个 token。这正是 CapSkip 在这里的工作,本指南其余部分就是把两者粘合起来的代码。

第 1 步:创建项目

在一个空文件夹里使用虚拟环境。这些命令通过路径直接运行该环境自己的 Python,所以你不需要激活它,受限的 PowerShell 脚本策略也不会造成妨碍。下面的每个脚本都用该环境的 Python 运行,例如 .\.venv\Scripts\python.exe solve.py。较旧的 Pydoll 安装(例如 2.27.0)会在下面脚本的第一次导入时失败,报 ImportError: cannot import name ‘Chrome’ from ‘pydoll’。Pydoll 3.0 从顶层导出 Chrome。

# Windows PowerShell, in an empty folder
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install pydoll-python capskip

第 2 步:启动 Chrome 并打开页面

把它保存为 solve.py。这是能打开你的页面并打印页面标题的最小 Pydoll 脚本。浏览器在 async with 代码块里启动,所以代码块结束时它会自动关闭。

# pip install pydoll-python capskip
import asyncio

from pydoll import Chrome
from pydoll.browser.options import ChromiumOptions

PAGE = 'https://example.com/page-with-recaptcha'


async def main():
    options = ChromiumOptions()
    options.headless = True  # set to False to watch the window
    async with Chrome(options=options) as browser:
        tab = await browser.start()
        await tab.go_to(PAGE)
        print(await tab.title())


asyncio.run(main())

Pydoll 只在两个位置查找 Chrome:Program Files 和 Program Files (x86),在 Google、Chrome、Application 目录下。为单个用户安装在 AppData 下的 Chrome 找不到,脚本会以 InvalidBrowserPath 停止。把 options.binary_location 设为 chrome.exe 的完整路径即可解决。不存在的路径则会以 FailedToStartBrowser 失败。同一行也能让 Pydoll 指向 Chrome for Testing,这样也能用。Pydoll 不会把浏览器版本和任何东西做匹配,所以 146 和 155 都能运行。

在 Windows 上,启动比你预想的要慢。在测试用 PC 上,使用默认设置和无头 Chrome 155 时,browser.start() 用了 2.8 秒。在没有外部脚本的页面上,第一次 go_to 又多用了 2.1 秒;在加载 Google 脚本的注册页面上,大约是 3.1 秒。Pydoll 连接的是 localhost,而 localhost 会先尝试 IPv6 地址。Chrome 的调试端口不监听 IPv6,Windows 要花大约 2 秒才会拒绝一个回环连接,所以每个到 Chrome 的新连接都要先付出大约 2 秒,然后 IPv4 才能连通。这既包括浏览器本身,也包括你打开的每一个额外标签页。Pydoll 没有针对它的设置。在 Python 进程内让 localhost 解析到 127.0.0.1,在测试中能消除这个延迟,但它会给标准库打补丁,所以本指南不碰它。给一次运行计时的时候,要把这一点算进去。

with 代码块会在正常退出、在块内发生异常之后,以及任务被取消之后关闭 Chrome,并删除临时配置文件夹。用 taskkill /F 结束 Python 进程则会跳过这一步。测试中,它留下了 13 个仍在运行的 chrome.exe 进程,配置文件夹也留在磁盘上。控制台里的 Ctrl+C 没有测试过。如果你强行结束过一次运行,在开始下一次之前,先到 Task Manager 里查看有没有残留的 chrome.exe 进程。

第 3 步:确认 CapSkip 有响应

在加入浏览器之前,先确认识别调用本身可以工作。把它保存为 first_solve.py。AsyncCapSkip 是同一个包里的异步客户端。SDK 不读取任何环境变量,所以主机和端口是直接写进客户端的。这些占位符是字面值。YOUR_SITEKEY 和页面地址必须来自一个确实带有 reCAPTCHA v2 小组件的页面,否则 CapSkip 会返回错误。从页面源码里复制 sitekey,它就是 data-sitekey 的值。第 4 步会替你从页面里读取它。用 .\.venv\Scripts\python.exe first_solve.py 运行这个文件。

# pip install capskip
import asyncio

from capskip import AsyncCapSkip


async def main():
    solver = AsyncCapSkip(host='127.0.0.1', port=8080)
    result = await solver.recaptcha(
        sitekey='YOUR_SITEKEY',
        url='https://example.com/page-with-recaptcha',
    )
    print(result['code'][:30])


asyncio.run(main())

第 4 步:从页面读取 sitekey

sitekey 就在页面上,Pydoll 可以读取它。普通的小组件是一个 div,带有 g-recaptcha 类和 data-sitekey 属性。由页面用 grecaptcha.render() 绘制的小组件没有这样的 div,它留下的是一个 iframe,其地址在 k 参数里带着 sitekey。这个函数用同一个选择器处理两种情况,并靠标签名判断拿到的是哪一种。把它加到 solve.py 里 async def main() 的上方。

from urllib.parse import parse_qs, urlparse


async def find_sitekey(tab):
    # A normal widget carries data-sitekey. A widget drawn by
    # grecaptcha.render() only leaves its iframe, with k=<sitekey>.
    found = await tab.query(
        '.g-recaptcha[data-sitekey], '
        'iframe[src*="recaptcha/api2/anchor"]',
        timeout=10,
    )
    if found.tag_name == 'iframe':
        src = found.get_attribute('src')
        return parse_qs(urlparse(src).query)['k'][0]
    return found.get_attribute('data-sitekey')

这里有两个 Pydoll 的细节很重要。get_attribute 是普通函数,不是协程,所以不要 await 它,属性不存在时它返回 None。另外,query 在没有匹配时会抛出 ElementNotFound。带上超时参数时,它会等待,并改为抛出 WaitElementTimeout,这对页面加载一会儿之后才出现的小组件是正确的选择。一个在页面加载 3 秒后才注入的小组件,用 find_sitekey 里 10 秒的超时,大约 3 秒后被找到了。

第 5 步:在同一个事件循环上识别

Pydoll 运行在 asyncio 上,你的识别调用也运行在同一个循环上。所以选哪个客户端很重要。把这段也加到 async def main() 的上方,第 6 步的函数同样这样做。

from capskip import AsyncCapSkip

solver = AsyncCapSkip(host='127.0.0.1', port=8080)


async def solve(sitekey, url):
    result = await solver.recaptcha(sitekey, url)
    return result['code']

同步的 CapSkip 客户端也能用,但它会在整个识别期间阻塞事件循环,在它等待的时候,Pydoll 就不再响应 Chrome。一次模拟运行统计了:在一次 3 秒的模拟识别期间,每隔 50 毫秒向页面询问时间时,页面应答了多少次。最长间隔是两次应答之间最长的时间:

客户端识别耗时计数器计数最长间隔
同步的 CapSkip 客户端,直接在异步函数里调用3.76 s53.78 s
asyncio.to_thread 中的 CapSkip3.76 s650.06 s
AsyncCapSkip5.0 s680.3 s

第一种情况下,页面本身一直在运行。冻住的只是 Python 这一侧:识别结束之前,Python 无法对页面做出的任何事情作出反应,所以来自第二个标签页的事件会被延迟处理。这里 AsyncCapSkip 多花了大约一秒,但这取决于识别结束后它的下一次轮询落在什么时候;在 2、4 和 8 秒的模拟识别下,它比线程版本更早完成。它始终要付出的代价是每个请求大约 0.15 秒的事件循环阻塞,因为它每次都会新建一个 HTTP 客户端。即便如此,也比冻住的循环好。

第 6 步:把 token 写进页面

识别完成的 reCAPTCHA 就是一个 token。页面期望它出现在名为 g-recaptcha-response 的 textarea 里;如果小组件有回调,页面还期望回调带着这个 token 运行。这个函数两件事都做。它使用 Pydoll 的 execute_script,并在找到并运行了回调时返回 True。也把它加到 async def main() 的上方。

import json

FILL_JS = """
(function (token) {
  var seen = new Set(), fn = null;
  document.querySelectorAll('textarea[name="g-recaptcha-response"]')
    .forEach(function (box) { box.value = token; });
  (function walk(o, depth) {
    if (!o || typeof o !== 'object' || seen.has(o) || depth > 6) return;
    seen.add(o);
    for (var k in o) {
      var v; try { v = o[k]; } catch (e) { continue; }
      if (k === 'callback' && !fn) {
        fn = typeof v === 'string' ? window[v] : v;
      }
      if (v && typeof v === 'object' && !(v instanceof Node)) {
        walk(v, depth + 1);
      }
    }
  })(window.___grecaptcha_cfg, 0);
  if (typeof fn === 'function') fn(token);
  return typeof fn === 'function';
})(%s)
"""


async def fill_token(tab, token):
    reply = await tab.execute_script(
        FILL_JS % json.dumps(token), return_by_value=True)
    outcome = reply['result']
    if 'exceptionDetails' in outcome:
        error = outcome['exceptionDetails']['exception']
        raise RuntimeError(error['description'])
    return outcome['result']['value']  # True if a callback ran

这段 JavaScript 会遍历 Google 自己的配置对象来找到回调,因为其中的键是 Google 可以随时更改的短名称。data-callback 属性在那里存的是函数名,所以遍历时会在 window 上查找这个名称。传给 grecaptcha.render() 的回调存的则是函数本身。两种情况都在 Google 真实的 api.js 上运行过。

Pydoll 从 execute_script 返回的是原始的 DevTools 应答,所以值位于 reply[‘result’][‘result’][‘value’],而且你必须 await 这个调用。除非传入 return_by_value=True,否则对象会以引用的形式返回。JavaScript 错误不会在 Python 里抛出。它会在应答里加上 exceptionDetails,所以这个函数会检查它,并抛出它自己的 RuntimeError。

第 7 步:组装起来

删掉 solve.py 底部的 async def main() 和 asyncio.run(main()) 那一行,保留它们上方的函数,然后把下面这段粘贴到原来的位置。它在页面加载之后才识别,因为 sitekey 来自页面,并且在 token 即将使用之前才识别。如果运行了回调,之后由页面接手。如果没有,脚本就点击表单的提交按钮,并等待页面跳转。

async def main():
    options = ChromiumOptions()
    options.headless = True  # set to False to watch the window
    async with Chrome(options=options) as browser:
        tab = await browser.start()
        await tab.go_to(PAGE)
        sitekey = await find_sitekey(tab)
        token = await solve(sitekey, await tab.current_url())
        if not await fill_token(tab, token):
            button = await tab.query('form button[type="submit"]')
            async with tab.expect_navigation():
                await button.click()
        print(await tab.title())


asyncio.run(main())

在由回调负责发送的页面上,没有什么可点击的。这个版本以同样的方式替换 main()。接下来会发生什么取决于页面,所以要等页面自己改变的某样东西。这个版本等待测试页面上的一个结果元素从 waiting 变成别的内容。请把选择器改成你的页面会更新的任何元素。

async def main():
    options = ChromiumOptions()
    options.headless = True  # set to False to watch the window
    async with Chrome(options=options) as browser:
        tab = await browser.start()
        await tab.go_to(PAGE)
        sitekey = await find_sitekey(tab)
        token = await solve(sitekey, await tab.current_url())
        if await fill_token(tab, token):
            # The page's own callback takes over. What it does next is
            # page specific: this page writes its answer into #result.
            await tab.wait_for_script(
                "var r = document.querySelector('#result');"
                "return r && r.textContent !== 'waiting';"
            )
        print(await (await tab.query('#result')).text())


asyncio.run(main())

完整脚本

下面是完整的 solve.py,所有 import 都在顶部。它取代你逐个添加的那些代码块,所以请先把那些删掉。在普通表单页面上运行时,它打印了 Welcome,页面接受了这个 token 一次。在由回调负责发送的页面上,这个版本可能在页面变化之前就打印出旧标题,所以那种页面请使用上面的回调版本。

# pip install pydoll-python capskip
import asyncio
import json
from urllib.parse import parse_qs, urlparse

from capskip import AsyncCapSkip
from pydoll import Chrome
from pydoll.browser.options import ChromiumOptions

PAGE = 'https://example.com/page-with-recaptcha'

solver = AsyncCapSkip(host='127.0.0.1', port=8080)

# Put the token in every response box, then run the page's callback.
FILL_JS = """
(function (token) {
  var seen = new Set(), fn = null;
  document.querySelectorAll('textarea[name="g-recaptcha-response"]')
    .forEach(function (box) { box.value = token; });
  (function walk(o, depth) {
    if (!o || typeof o !== 'object' || seen.has(o) || depth > 6) return;
    seen.add(o);
    for (var k in o) {
      var v; try { v = o[k]; } catch (e) { continue; }
      if (k === 'callback' && !fn) {
        fn = typeof v === 'string' ? window[v] : v;
      }
      if (v && typeof v === 'object' && !(v instanceof Node)) {
        walk(v, depth + 1);
      }
    }
  })(window.___grecaptcha_cfg, 0);
  if (typeof fn === 'function') fn(token);
  return typeof fn === 'function';
})(%s)
"""


async def find_sitekey(tab):
    # A normal widget carries data-sitekey. A widget drawn by
    # grecaptcha.render() only leaves its iframe, with k=<sitekey>.
    found = await tab.query(
        '.g-recaptcha[data-sitekey], '
        'iframe[src*="recaptcha/api2/anchor"]',
        timeout=10,
    )
    if found.tag_name == 'iframe':
        src = found.get_attribute('src')
        return parse_qs(urlparse(src).query)['k'][0]
    return found.get_attribute('data-sitekey')


async def solve(sitekey, url):
    result = await solver.recaptcha(sitekey, url)
    return result['code']


async def fill_token(tab, token):
    reply = await tab.execute_script(
        FILL_JS % json.dumps(token), return_by_value=True)
    outcome = reply['result']
    if 'exceptionDetails' in outcome:
        error = outcome['exceptionDetails']['exception']
        raise RuntimeError(error['description'])
    return outcome['result']['value']  # True if a callback ran


async def main():
    options = ChromiumOptions()
    options.headless = True  # set to False to watch the window
    async with Chrome(options=options) as browser:
        tab = await browser.start()
        await tab.go_to(PAGE)
        sitekey = await find_sitekey(tab)
        token = await solve(sitekey, await tab.current_url())
        if not await fill_token(tab, token):
            button = await tab.query('form button[type="submit"]')
            async with tab.expect_navigation():
                await button.click()
        print(await tab.title())


asyncio.run(main())

同时识别两个标签页

两个标签页可以用 asyncio.gather 同时识别。两个同时开始的 3 秒识别,在不到 4 秒内一起完成,得到了不同的 token。陷阱在于 cookie。默认浏览器上下文里的标签页共用一个 cookie 存储,所以第二个页面加载可能会替换掉第一个标签页即将用来提交的会话或 CSRF cookie。模拟站点在每次页面加载时都设置一个新的会话 cookie(一些真实站点也是这样),所以第二次加载替换了第一个标签页的 cookie,两个同时提交中有一个被拒绝了。给每个标签页各自的浏览器上下文就解决了问题,两次提交都被接受了。这个版本以同样的方式替换 main(),而且它会点击提交,所以适合普通表单。

async def solve_page(tab):
    sitekey = await find_sitekey(tab)
    token = await solve(sitekey, await tab.current_url())
    await fill_token(tab, token)
    button = await tab.query('form button[type="submit"]')
    async with tab.expect_navigation():
        await button.click()
    return await tab.title()


async def main():
    options = ChromiumOptions()
    options.headless = True
    async with Chrome(options=options) as browser:
        await browser.start()
        # One browser context per tab: tabs in the default context
        # share cookies, and a second page load can replace the first
        # tab's session or CSRF cookie.
        contexts = [await browser.create_browser_context() for _ in range(2)]
        tabs = [
            await browser.new_tab(PAGE, browser_context_id=context)
            for context in contexts
        ]
        print(await asyncio.gather(*[solve_page(tab) for tab in tabs]))
        for context in contexts:
            await browser.delete_browser_context(context)


asyncio.run(main())

对另一台机器上的 CapSkip 运行 Pydoll

Pydoll 和识别工具不必共用一台 PC。在连接设置里把 CapSkip 切换到 Server 模式,它就会监听你的网络地址或公网 IP。建议使用静态公网 IP,以便稳定地远程访问。之后只有客户端那一行需要改。识别工具仍然是你自己的 Windows 机器,识别也仍然不计量。

# CapSkip on another Windows PC, in Server mode
solver = AsyncCapSkip(
    host='192.168.1.50',  # that PC's network address
    port=8080,
    apiKey='YOUR_API_KEY',  # only if API key validation is on
)

有两点要注意。SDK 通过普通 HTTP 与 CapSkip 通信,所以 API 密钥是以明文传输的。把这条链路放在专用网络或 VPN 里,并开启 API key validation。另外,Server 模式启动时,CapSkip 会向所有地址开放端口,所以要缩小范围,办法是为你不预期的地址添加一条 Windows Firewall 阻止规则。使用 AsyncCapSkip 时,无法连接的地址大约 5 秒后失败,抛出一个消息为空的 NetworkException。本指南没有运行远程设置,只读取了构造函数。

超时,以及失败是什么样子

要快速地先识别,再提交。reCAPTCHA token 只能用一次,而且会过期,所以要在页面加载之后识别,并且就在提交之前。严格的模拟站点对同一个 token 的第二次提交返回了 403,这是模拟程序自己的规则。具体的时效见 reCAPTCHA token 过期时间 页面。下面是在模拟程序下,使用 AsyncCapSkip 和默认设置时,每种失败的样子:

情况你所看到的经过时间
CapSkip 没有在运行NetworkException: All connection attempts failed2.2 s
CapSkip 接受了调用,然后毫无回应NetworkException,消息为空5.3 s
这次识别无法完成ApiException: ERROR_CAPTCHA_UNSOLVABLE0.7 s
一次 30 秒的识别,recaptchaTimeout 设为 6TimeoutException: timeout 6.0 exceeded8.7 s
同样的识别,放在 4 秒的 asyncio.wait_for 里Python 自己的 TimeoutError(Python 3.10 上是 asyncio.TimeoutError)4.0 s

要在客户端上设置 recaptchaTimeout,而不是在调用上。给调用传 timeout 参数会引发 TypeError,消息是 got multiple values for keyword argument ‘timeout’。SDK 只在两次轮询之间检查它的时限,所以 6 秒的限制是在 8.7 秒时才触发的。如果需要硬性上限,就把调用包在 asyncio.wait_for 里。那只会取消你这一侧。CapSkip 会把这个任务保留到它自己的限制为止,这一点没有用真实的应用测试过。Pydoll 有它自己的错误。导航耗时超过 go_to 允许的时间,会抛出 PageLoadTimeout;连接被拒绝则会抛出 NavigationError,消息里带有 net::ERR_CONNECTION_REFUSED。Pydoll 里也有一个 TimeoutException 类,CapSkip 里有一个同名的类。它们是不同的类,所以请导入模块,并在捕获识别工具的那个时写成 capskip.TimeoutException。

Pydoll 验证码的常见错误

你所看到的原因修复
ImportError: cannot import name ‘Chrome’ from ‘pydoll’安装的是 Pydoll 2.x升级到 pydoll-python 3
InvalidBrowserPath: No valid browser path foundChrome 是为单个用户安装的,不在 Program Files 里设置 options.binary_location
FailedToStartBrowser: The browser exited with code 21固定的 user-data-dir 已被另一个浏览器占用换一个配置文件夹,或者不指定
ArgumentAlreadyExistsInOptions同一个 ChromiumOptions 对象被重复用于第二个浏览器,或者你添加了一个 Pydoll 自己会添加的参数为每个浏览器新建一个 options 对象
sitekey 查询时出现 WaitElementTimeout页面上还没有小组件,或者选择器与它不匹配在可见窗口中检查页面,然后调大超时
NetworkException: All connection attempts failedCapSkip 没有在运行,或者端口不对启动 CapSkip 并检查连接设置
ApiException: ERROR_CAPTCHA_UNSOLVABLECapSkip 无法完成这次识别检查 sitekey 和页面地址,然后重试一次。参见 ERROR_CAPTCHA_UNSOLVABLE
RuntimeError: ReferenceError,某个名称未定义fill_token 里的 JavaScript 在页面内部抛出了异常在浏览器控制台里运行同一段脚本
页面提示验证码失败,Python 里没有错误token 被使用了两次,或者回调没有运行在每次提交之前重新识别
FailedToStartBrowser: Could not launchoptions.binary_location 指向的文件不存在检查 chrome.exe 的路径
每次运行都比实际工作多出大约 4 秒每个到 Chrome 的新连接都要付出大约 2 秒的 localhost 延迟复用同一个浏览器和同一个标签页:同一标签页上的第二次 go_to 用了不到 0.2 秒

常见问题

Pydoll 自己能识别 reCAPTCHA 吗?

不能。Pydoll 3.0.0 没有 reCAPTCHA 代码。它唯一的验证码辅助函数是查找 Cloudflare Turnstile 复选框并点击它,这不是识别,也不返回 token。

这能和无头 Chrome 一起用吗?

完整脚本在无头和有头两种模式下,都在模拟页面上通过了,使用的是 Chrome 155 和 Chrome for Testing 146。线上站点如何对待无头浏览器没有测试过,那是和 token 不同的另一个问题。

我可以把 Pydoll 跑在另一台 PC 上,而 CapSkip 留在家里吗?

可以,只要那台 PC 能访问到 CapSkip。把 CapSkip 设为 Server 模式,并在客户端里设置主机和端口。建议使用静态公网 IP,以便通过互联网稳定地远程访问。密钥和页面地址通过普通 HTTP 以明文传输,所以最好用 VPN 或专用网络,并为其他地址添加一条 Windows Firewall 阻止规则。本指南没有测试任何远程设置。

隐形 reCAPTCHA、v3 和 Enterprise 呢?

SDK 的 recaptcha 调用为它们提供了选项:invisible 设为 1,enterprise 设为 1,version 设为 v3。Pydoll 这一侧需要不同的页面读取方式,例如 v3 的 action 名称,而这些变体本指南都没有运行过。关于 v3 的识别调用本身,请参阅 在 Python 中识别 reCAPTCHA v3.

简短版结论

一个 Pydoll 验证码流程有五个部分:从 data-sitekey 或 iframe 地址读出 sitekey;用 AsyncCapSkip 识别,让事件循环保持运转;把 token 写进 g-recaptcha-response;运行页面的回调或提交;并给每个并行标签页各自的浏览器上下文。要在提交之前才识别,并在客户端上设置 recaptchaTimeout,再在调用外面套上 asyncio.wait_for 作为硬性上限。

浏览器爬取往往远在变贵之前就先变慢了,而把 本地验证码识别工具 放在你自己的 Windows 机器上,识别就完全不会产生账单。