如何用 PHP 识别 reCAPTCHA v2(含隐形版)

solve recaptcha v2 in php - How to Solve reCAPTCHA v2 in PHP, Including Invisible

reCAPTCHA v2 有三种变体,在 PHP 中它们是一个带选项数组的方法。复选框是最简单的调用,隐形和 Enterprise 是标志,两者也可以同时设置。PHP 还是唯一一个完全没有异步方案的 CapSkip SDK,这简化了代码,但也改变了你组织批量工作的方式。

设置

# 需要 PHP 8.0 或更高版本,并需要 curl 和 json 扩展,
# 这两个扩展几乎在每个 PHP 安装中都自带。
composer require capskip/capskip

CapSkip 在你自己的机器上识别,因此必须运行桌面应用。将客户端指向其设置中显示的端口:

use CapSkip\CapSkip;

$solver = new CapSkip([
    'apiKey' => 'capskip',        // any string when key validation is off
    'host' => '127.0.0.1',
    'port' => 8080,
    'recaptchaTimeout' => 300,    // seconds
]);

在生产环境中,从环境变量读取这些值:

use CapSkip\CapSkip;

$solver = new CapSkip([
    'apiKey' => getenv('CAPSKIP_API_KEY') ?: 'capskip',
    'host' => getenv('CAPSKIP_HOST') ?: '127.0.0.1',
    'port' => (int) (getenv('CAPSKIP_PORT') ?: 8080),
]);

三种变体

变体需添加的选项
复选框
隐形['invisible' => 1]
Enterprise['enterprise' => 1]
隐形 Enterprise两个键都加
// Checkbox: sitekey and page URL only.
$result = $solver->recaptcha(
    '6Lc...YOUR_SITEKEY',
    'https://example.com/login'
);

echo $result['code'];   // g-recaptcha-response token

// Invisible.
$result = $solver->recaptcha($sitekey, $pageUrl, ['invisible' => 1]);

// Enterprise, and both together.
$result = $solver->recaptcha($sitekey, $pageUrl, ['enterprise' => 1]);
$result = $solver->recaptcha($sitekey, $pageUrl, [
    'enterprise' => 1,
    'invisible' => 1,
]);

返回值是一个关联数组,因此 $result['code'] 就是 token。 $result['captchaId'] 也一并提供,如果你想记录是哪次识别产生了它。

正确获取输入

sitekey 是 data-sitekey 小组件容器上的属性,或者是传给 grecaptcha.render 的第一个参数——在隐形模式下没有容器可查看时。它始终以 6L 开头,且是公开的。

URL 必须是小组件实际渲染所在的页面。传入你的表单处理程序或登录后的重定向,是 token 识别顺利却随后验证失败的常见原因。

提交 token

$ch = curl_init('https://example.com/login');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'g-recaptcha-response' => $result['code'],
        'username' => '...',
        'password' => '...',
    ]),
]);

$response = curl_exec($ch);
curl_close($ch);

token 只能使用一次,有效期约两分钟,因此请在流程中尽可能晚地识别。如果网站把 token 交给 JavaScript 回调而不是表单字段,识别相同但提交方式不同。我们的 reCAPTCHA v2 回调识别工具 页面介绍了这种方式。

错误,以及人们容易忽略的命名空间

PHP 把它的异常放在各自的命名空间下,这会让从 Python 或 Node 示例复制导入语句的人栽跟头:

use CapSkip\CapSkip;
use CapSkip\Exceptions\ValidationException;
use CapSkip\Exceptions\NetworkException;
use CapSkip\Exceptions\ApiException;
use CapSkip\Exceptions\TimeoutException;

try {
    $result = $solver->recaptcha($sitekey, $pageUrl);
} catch (ValidationException $e) {
    // missing or malformed arguments
} catch (NetworkException $e) {
    // CapSkip is not running on the configured port
} catch (ApiException $e) {
    // the sitekey or pageurl was rejected
} catch (TimeoutException $e) {
    // exceeded recaptchaTimeout
}

这四个都继承自 CapSkip\Exceptions\CapSkipError,因此如果你更愿意在一处处理失败,只需对基类做一次 catch 即可处理全部情况。

识别多个

PHP 是同步执行的,因此每次识别都会阻塞直到完成:

use CapSkip\CapSkip;

$solver = new CapSkip();

foreach ($targets as $target) {
    $results[] = $solver->recaptcha($target['sitekey'], $target['url']);
}

该包导出了 AsyncCapSkip,但在 PHP 中它只是一个别名,只是为了让从其他 SDK 移植的代码继续运行。它不会增加并发。要实现真正的并行,请运行多个工作进程或使用队列,而不要指望 SDK 让识别相互重叠。

使用代理

$result = $solver->recaptcha($sitekey, $pageUrl, [
    'proxy' => ['type' => 'HTTPS', 'uri' => 'user:[email protected]:3128'],
]);

reCAPTCHA、Turnstile 和 极验 支持代理,但图片验证码不支持,因为它们是从图片字节中识别的,永远不会到达目标网站。

常见问题

我需要轮询结果吗?

不需要。SDK 会在内部轮询并返回完成的 token,因此 CAPCHA_NOT_READY 永远不会出现在你的代码里。它在 250 毫秒后开始检查,并从那里逐步退避。

识别会阻塞我的 Web 请求吗?

会,而且这在 PHP 中很关键。一次 reCAPTCHA 识别可能耗时数秒,因此在页面渲染中执行会占用一个工作进程。请把识别放到队列作业或 CLI 工作进程中,而不要阻塞请求线程。

有哪些要求?

PHP 8.0 或更新版本,并启用 curljson 扩展,这两者在大多数安装中都已捆绑。没有其他运行时依赖,因此它可以直接接入现有项目,而不会拉入一大堆包。

小结

一个方法、三种变体,通过 invisibleenterprise 放入选项数组。读取 $result['code'],将其作为 g-recaptcha-response,从以下模块导入异常: CapSkip\Exceptions导入异常,并让识别远离你的请求线程。

更完整的 PHP 接口见 PHP 验证码识别 页面,其他语言见 reCAPTCHA v2 识别 页面,还有一个 在线 v2 演示 可供测试。CapSkip 采用 本地验证码识别工具,因此不会按次识别收费。