想在 Windows 上用 PowerShell 直接调用 CaptchaAI 识别验证码?不用装任何第三方模块——Invoke-RestMethod 在 PowerShell 5.1 和 7+ 里都是内置的,几行代码就能提交任务、轮询结果、拿到 token。
- 封装两个基础函数:提交任务、轮询结果
- 分别识别 reCAPTCHA v2/v3、Turnstile、图片验证码
- 打包成模块,接入 Windows 计划任务定时执行(运维、QA、DevOps 团队常用于 Web 测试与表单自动化)
Windows 环境下为什么首选 PowerShell 处理验证码
| 特性 | 说明 |
|---|---|
| 系统自带 | Windows 内置 PowerShell 5.1,无需额外安装 |
| Invoke-RestMethod 原生支持 REST | 自动解析 JSON |
| 任务计划程序 | 脚本可直接挂定时任务,不依赖第三方调度工具 |
| 管道友好 | 输出能直接传给下游逻辑 |
| 跨平台 | PowerShell 7+ 同样能在 Linux/macOS 上运行 |
准备工作
- PowerShell 5.1(Windows 自带)或 PowerShell 7+(跨平台)
- 一个 CaptchaAI API Key(点此获取)
- 不需要安装任何额外模块
两个基础函数:提交任务与轮询结果
提交任务:Submit-CaptchaTask
function Submit-CaptchaTask {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[hashtable]$TaskParams
)
$body = @{
key = $ApiKey
json = 1
} + $TaskParams
$response = Invoke-RestMethod -Uri "https://ocr.captchaai.com/in.php" `
-Method Post `
-Body $body `
-ContentType "application/x-www-form-urlencoded"
if ($response.status -ne 1) {
throw "Submit failed: $($response.request)"
}
return $response.request
}
把 Key、json = 1 和任务参数拼成表单 POST 到 in.php,status 不是 1 就抛异常。
轮询结果:Get-CaptchaResult
function Get-CaptchaResult {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$TaskId,
[int]$MaxWaitSeconds = 300,
[int]$PollIntervalSeconds = 5
)
$deadline = (Get-Date).AddSeconds($MaxWaitSeconds)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Seconds $PollIntervalSeconds
$response = Invoke-RestMethod -Uri "https://ocr.captchaai.com/res.php" `
-Method Get `
-Body @{
key = $ApiKey
action = "get"
id = $TaskId
json = 1
}
if ($response.request -eq "CAPCHA_NOT_READY") {
Write-Verbose "Waiting for solution..."
continue
}
if ($response.status -ne 1) {
throw "Solve failed: $($response.request)"
}
return $response.request
}
throw "Timeout: CAPTCHA not solved within $MaxWaitSeconds seconds"
}
默认每 5 秒查一次 res.php,看到 CAPCHA_NOT_READY 就继续等,最长等 300 秒,可用 -PollIntervalSeconds / -MaxWaitSeconds 调整。
识别 reCAPTCHA v2:完整示例
function Solve-RecaptchaV2 {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$SiteUrl,
[Parameter(Mandatory)]
[string]$SiteKey
)
Write-Host "Submitting reCAPTCHA v2 task..."
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "userrecaptcha"
googlekey = $SiteKey
pageurl = $SiteUrl
}
Write-Host "Task ID: $taskId"
Write-Host "Polling for solution..."
$token = Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
Write-Host "Solved! Token: $($token.Substring(0, [Math]::Min(50, $token.Length)))..."
return $token
}
# Usage
$apiKey = "YOUR_API_KEY"
$token = Solve-RecaptchaV2 `
-ApiKey $apiKey `
-SiteUrl "https://staging.example.com/qa-login" `
-SiteKey "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
SiteUrl 用的是自有的 staging.example.com/qa-login;Substring 只是截断打印,避免日志里出现完整 token。
识别 Cloudflare Turnstile
function Solve-Turnstile {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$SiteUrl,
[Parameter(Mandatory)]
[string]$SiteKey
)
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "turnstile"
key = $SiteKey
pageurl = $SiteUrl
}
return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}
# Usage
$token = Solve-Turnstile `
-ApiKey "YOUR_API_KEY" `
-SiteUrl "https://example.com/form" `
-SiteKey "0x4AAAAAAAB5..."
参数名和 reCAPTCHA 不一样:站点标识是 key,不是 googlekey。
识别 reCAPTCHA v3(按分数判定)
function Solve-RecaptchaV3 {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$SiteUrl,
[Parameter(Mandatory)]
[string]$SiteKey,
[string]$Action = "verify",
)
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "userrecaptcha"
googlekey = $SiteKey
pageurl = $SiteUrl
version = "v3"
action = $Action
}
return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}
v3 没有点选框,返回的是分数;action 参数要跟页面里 grecaptcha.execute 用的 action 一致,否则分数会不准。
识别图片验证码
function Solve-ImageCaptcha {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$ImagePath
)
if (-not (Test-Path $ImagePath)) {
throw "Image file not found: $ImagePath"
}
$imageBytes = [System.IO.File]::ReadAllBytes($ImagePath)
$base64 = [Convert]::ToBase64String($imageBytes)
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "base64"
body = $base64
}
return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}
# Usage
$text = Solve-ImageCaptcha -ApiKey "YOUR_API_KEY" -ImagePath "C:\captcha.png"
Write-Host "CAPTCHA text: $text"
method 传 base64,图片转成字符串直接提交,不用额外的文件上传接口。
直接从 URL 识别
function Solve-ImageCaptchaFromUrl {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$ImageUrl
)
$imageBytes = (Invoke-WebRequest -Uri $ImageUrl).Content
$base64 = [Convert]::ToBase64String($imageBytes)
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "base64"
body = $base64
}
return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}
图片是远程地址时,用 Invoke-WebRequest 转 Base64,其余逻辑不变。
封装成完整的 PowerShell 模块
- 复用前面所有函数逻辑,不用每个脚本重复写一遍
- 统一管理 API Key、超时时间等配置
- 另存为
CaptchaAI.psm1,跨脚本直接 import
class CaptchaAISolver {
[string]$ApiKey
[string]$BaseUrl = "https://ocr.captchaai.com"
[int]$PollInterval = 5
[int]$MaxWait = 300
CaptchaAISolver([string]$apiKey) {
$this.ApiKey = $apiKey
}
[string] SolveRecaptchaV2([string]$siteUrl, [string]$siteKey) {
return $this.Solve(@{
method = "userrecaptcha"
googlekey = $siteKey
pageurl = $siteUrl
})
}
[string] SolveTurnstile([string]$siteUrl, [string]$siteKey) {
return $this.Solve(@{
method = "turnstile"
key = $siteKey
pageurl = $siteUrl
})
}
[string] SolveImage([string]$imagePath) {
$bytes = [System.IO.File]::ReadAllBytes($imagePath)
$base64 = [Convert]::ToBase64String($bytes)
return $this.Solve(@{
method = "base64"
body = $base64
})
}
[double] GetBalance() {
$response = Invoke-RestMethod -Uri "$($this.BaseUrl)/res.php" `
-Body @{ key = $this.ApiKey; action = "getbalance"; json = 1 }
return [double]$response.request
}
hidden [string] Solve([hashtable]$params) {
$taskId = $this.Submit($params)
return $this.Poll($taskId)
}
hidden [string] Submit([hashtable]$params) {
$body = @{ key = $this.ApiKey; json = 1 } + $params
$response = Invoke-RestMethod -Uri "$($this.BaseUrl)/in.php" `
-Method Post -Body $body
if ($response.status -ne 1) { throw "Submit: $($response.request)" }
return $response.request
}
hidden [string] Poll([string]$taskId) {
$deadline = (Get-Date).AddSeconds($this.MaxWait)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Seconds $this.PollInterval
$response = Invoke-RestMethod -Uri "$($this.BaseUrl)/res.php" `
-Body @{ key = $this.ApiKey; action = "get"; id = $taskId; json = 1 }
if ($response.request -eq "CAPCHA_NOT_READY") { continue }
if ($response.status -ne 1) { throw "Solve: $($response.request)" }
return $response.request
}
throw "Timeout"
}
}
# Export
Export-ModuleMember
调用模块
using module .\CaptchaAI.psm1
$solver = [CaptchaAISolver]::new("YOUR_API_KEY")
# Check balance
$balance = $solver.GetBalance()
Write-Host "Balance: `$$balance"
# Solve reCAPTCHA v2
$token = $solver.SolveRecaptchaV2("https://staging.example.com/qa-login", "SITEKEY")
Write-Host "Token: $($token.Substring(0, 50))..."
封装之后只需 [CaptchaAISolver]::new() 一次,查余额、识别各类型都是同一对象的方法。
把 token 塞进表单一起提交
function Submit-FormWithToken {
param(
[string]$Url,
[string]$Token,
[hashtable]$FormData
)
$body = $FormData + @{
"g-recaptcha-response" = $Token
}
$response = Invoke-WebRequest -Uri $Url `
-Method Post `
-Body $body `
-ContentType "application/x-www-form-urlencoded"
return $response
}
# Usage
$token = Solve-RecaptchaV2 -ApiKey "YOUR_API_KEY" `
-SiteUrl "https://staging.example.com/qa-login" `
-SiteKey "SITEKEY"
$result = Submit-FormWithToken `
-Url "https://staging.example.com/qa-login" `
-Token $token `
-FormData @{
username = "[email protected]"
password = "password"
}
Write-Host "Response: $($result.StatusCode)"
把 token 塞进 g-recaptcha-response 字段一起 POST 出去,字段名要和表单要求一致。
用 PowerShell Jobs 并行处理多个站点
$apiKey = "YOUR_API_KEY"
$tasks = @(
@{ Url = "https://site-a.com"; Key = "SITEKEY_A" },
@{ Url = "https://site-b.com"; Key = "SITEKEY_B" },
@{ Url = "https://site-c.com"; Key = "SITEKEY_C" }
)
$jobs = $tasks | ForEach-Object {
$task = $_
Start-Job -ScriptBlock {
param($ApiKey, $Url, $SiteKey)
$taskId = (Invoke-RestMethod -Uri "https://ocr.captchaai.com/in.php" -Method Post -Body @{
key = $ApiKey; json = 1; method = "userrecaptcha"
googlekey = $SiteKey; pageurl = $Url
}).request
$deadline = (Get-Date).AddSeconds(300)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Seconds 5
$result = Invoke-RestMethod -Uri "https://ocr.captchaai.com/res.php" -Body @{
key = $ApiKey; action = "get"; id = $taskId; json = 1
}
if ($result.request -ne "CAPCHA_NOT_READY" -and $result.status -eq 1) {
return @{ Url = $Url; Token = $result.request }
}
}
return @{ Url = $Url; Error = "Timeout" }
} -ArgumentList $apiKey, $task.Url, $task.Key
}
# Wait and collect results
$results = $jobs | Wait-Job | Receive-Job
$results | ForEach-Object {
if ($_.Token) {
Write-Host "$($_.Url): $($_.Token.Substring(0, 50))..."
} else {
Write-Host "$($_.Url): $($_.Error)" -ForegroundColor Red
}
}
$jobs | Remove-Job
Start-Job 给每个站点起一个后台作业,互不阻塞,Wait-Job | Receive-Job 统一收集结果。站点多的时候比顺序调用快得多——国内电商团队做多站点监控常用这个模式,几十个站点并行几分钟就能跑完一轮。
失败重试:指数退避
function Solve-WithRetry {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[hashtable]$TaskParams,
[int]$MaxRetries = 3
)
$retryableErrors = @(
"ERROR_NO_SLOT_AVAILABLE",
"ERROR_CAPTCHA_UNSOLVABLE"
)
for ($attempt = 0; $attempt -le $MaxRetries; $attempt++) {
if ($attempt -gt 0) {
$delay = [Math]::Pow(2, $attempt) + (Get-Random -Maximum 3)
Write-Host "Retry $attempt/$MaxRetries after $($delay)s..."
Start-Sleep -Seconds $delay
}
try {
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams $TaskParams
$result = Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
return $result
}
catch {
$errorMsg = $_.Exception.Message
$isRetryable = $retryableErrors | Where-Object { $errorMsg -like "*$_*" }
if (-not $isRetryable -or $attempt -eq $MaxRetries) {
throw
}
Write-Warning "Retryable error: $errorMsg"
}
}
}
只有 ERROR_NO_SLOT_AVAILABLE 和 ERROR_CAPTCHA_UNSOLVABLE 才值得重试,其他错误直接抛出去。
接入任务计划程序,定时执行
# Create a scheduled task that runs CAPTCHA automation daily
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -File C:\Scripts\captcha-automation.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "08:00"
Register-ScheduledTask `
-TaskName "CaptchaAutomation" `
-Action $action `
-Trigger $trigger `
-Description "Run daily CAPTCHA automation with CaptchaAI"
-File 换成你自己脚本的实际路径即可,注册后每天定时自动运行,无需人工干预。
常见报错排查
| 错误 | 原因 | 处理方式 |
|---|---|---|
ERROR_WRONG_USER_KEY |
API 密钥无效 | 在仪表板上验证密钥 |
ERROR_ZERO_BALANCE |
没有资金 | 充值账户 |
Invoke-RestMethod: SSL/TLS |
TLS 版本不匹配 | 添加[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
The response content cannot be parsed |
非 JSON 响应 | 使用Invoke-WebRequest并手动解析 |
Execution policy 错误 |
脚本被阻止 | 运行Set-ExecutionPolicy -Scope CurrentUser RemoteSigned |
Cannot convert to double |
平衡解析错误 | 使用[double]::Parse($response.request) |
常见问题
Invoke-RestMethod 返回的 status 一直是 0,怎么排查?
看 response.request 的错误字符串:ERROR_WRONG_USER_KEY 是 Key 填错,ERROR_ZERO_BALANCE 是没钱,都不是网络问题。
API Key 该怎么在脚本里安全存放,不写死在源码里?
本地跑用环境变量 $env:CAPTCHAAI_KEY;计划任务或 CI 就存成安全变量,别明文提交进版本库。
轮询一直卡在 CAPCHA_NOT_READY,是超时设置的问题吗?
不一定,这是正常的中间状态,会按 PollIntervalSeconds 继续等;长时间不变就先查 pageurl/googlekey。
用任务计划程序跑脚本时报 TLS 握手错误,要怎么处理?
脚本最前面加一行 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 即可解决。
没有管理员权限的 Windows Server,还能跑这套脚本吗?
能,识别验证码只是普通 HTTP 请求。真正要权限的是改执行策略,用 Set-ExecutionPolicy -Scope CurrentUser RemoteSigned 即可。
相关指南
马上开始: 获取你的 API Key,从 Windows 命令行动手写第一个脚本。