如何在 Activepieces 中识别验证码(HTTP piece)

在 Activepieces 里识别一次验证码就三步:提交挑战、等待、读取 token。把它搭在 HTTP piece 上,而不是 Code 步骤上,因为 Code 步骤能不能用 npm,取决于你的实例跑在哪种沙箱模式下,而 Activepieces Cloud 用的那种模式没有 npm。HTTP piece 在每种模式下都能用。另外还有一个设置比代码本身更要紧,它决定了你的流程究竟能不能访问到私有地址上的识别程序。
你需要什么
- 一个可以发布流程的 Activepieces 项目,用他们的云或自托管都行。
- CapSkip 运行在一台 Windows 机器上。只有当 Activepieces 也跑在同一台机器上时,Local 模式才够用,实际上这意味着自托管安装。其他情况都需要 Server 模式。
- 你要自动化的站点的 sitekey 和页面 URL。
- 一个保存识别程序密钥的项目变量,这样密钥就不会写在流程正文里。
- 如果自托管实例做了网络加固,还需要在 SSRF 白名单里加一条记录。第 3 步会讲这个。
为什么用 HTTP piece 而不是 Code 步骤
Code 步骤的编辑器里有一个 Add npm package 对话框。它会在 npm registry 里查找这个包,锁定最新版本,并写进该步骤的依赖列表。而在 Activepieces Cloud 上,这个列表随后会被丢弃。
Activepieces 构建 Code 步骤的方式是:把你的源码写进一个 TypeScript 文件,安装它的依赖,再把结果打包。只有当实例的执行模式允许使用包时,构建过程才会去取你声明的依赖。在 V8 沙箱模式下不允许使用包,而 Activepieces 文档里写明他们的云用的就是这种模式,于是构建会替换成一份空依赖集合,照样编译完成。步骤会顺利部署。等流程真正运行时,import 才失败。
| 执行模式 | Code 步骤里的 npm | 这对本文的流程意味着什么 |
|---|---|---|
| V8 沙箱,取值 SANDBOX_CODE_ONLY | 不支持 npm 包 | 用 HTTP piece。Activepieces Cloud 就是这种 |
| 组合沙箱,SANDBOX_CODE_AND_PROCESS | 不支持 npm 包 | 用 HTTP piece |
| 内核命名空间,SANDBOX_PROCESS | npm 包可用 | Node SDK 可用,而且它会替你轮询 |
| 无沙箱,UNSANDBOXED | npm 包可用 | Node SDK 可用,而且它会替你轮询 |
所以老实说,这件事有两条路,而走哪条不由你决定,取决于你的管理员。下面这条 HTTP piece 路线在四种模式下都能走。本文末尾那条 Code 步骤路线只在其中两种下可行,但只要能用,它要短得多。
第 1 步:提交挑战
CapSkip 在 8080 端口上提供兼容 2captcha 的 API,所以 HTTP piece 不用装任何连接器就能和它对话。添加一个 Send HTTP Request 操作,把方法设为 POST,URL 设为你识别程序上的提交接口。
{
"key": "{{variables['CAPSKIP_KEY']}}",
"method": "userrecaptcha",
"googlekey": "YOUR_SITEKEY",
"pageurl": "https://example.com/page-with-recaptcha",
"json": 1
}响应是一个很小的 JSON 对象,其中的 request 字段就是你后面轮询要用的 id。
{ "status": 1, "request": "2122988149" }这是 reCAPTCHA v2。CapSkip 支持的其他类型都是同一个调用,只是参数不同:加上 invisible 或 enterprise 设为 1,或者 version 设为 v3 并带上 action 名称,又或者把 method 换成 turnstile 或 geetest。完整参数列表见 CapSkip API 文档.
用项目变量语法引用密钥,不要直接把它粘贴进去。变量在存储时是加密的,在变量列表里也不会再显示给你看,而且轮换密钥只需改一处,不用把每个流程都翻一遍。
第 2 步:等待,然后读取 token
添加一个 Delay For 操作,然后再加一个读取结果的 HTTP 请求。对 reCAPTCHA v2 复选框来说,20 秒是合理的首次等待时长。图片验证码大约 1 秒就有结果,v3 是 10 到 15 秒,极验(GeeTest)大约 5 秒。
# GET, with the id from step 1 in the query string. http://127.0.0.1:8080/res.php?key=YOUR_KEY&action=get&id=2122988149&json=1
有两种可能的返回。已就绪的结果是一个和提交响应形状相同的 JSON 对象,token 在 request 字段里。还没就绪时返回的是字符串 CAPCHA_NOT_READY,拼写里少了一个 T,它的意思是继续等,而不是出了什么问题。这个拼写的来历见 一篇关于 CAPCHA_NOT_READY 响应的完整说明.
Delay piece 在 10 秒这条界线两侧的行为不一样,正是这个细节让这里的轮询写法变得很便宜。10 秒及以内的延时是在 worker 进程里睡眠。超过 10 秒则会创建一个 waitpoint,把这次运行挂起,等时间到了再恢复。挂起的时间不算执行时间,Activepieces 的文档也写明:被 Delay 或 Wait for Approval 暂停的流程不计入运行超时。所以 20 秒的等待不会占用你那 10 分钟的额度,再等第二次同样不占。
如果读一次不够,就再加一个 Delay 和一次读取,而不是去用循环。有两个原因。Loop on Items 会把列表里的每一项都跑一遍,所以不管 token 是否已经拿到,这些迭代都会发生,而在循环里放一个 Router,省掉的只是分支里的那点活,并不能省掉绕这一圈。更重要的是,CapSkip 的结果只能读取一次,所以一个去重读它已经取回过的 id 的循环,不会把 token 拿到两次,第二次读到的是一个错误。
第 3 步:会挡住本地识别程序的那个网络设置
这一段最容易把人绊住,而且它是 Activepieces 特有的,不是放之四海皆准的编排工具经验。Activepieces 针对流程代码有一道 SSRF 防护,由名为 AP_NETWORK_MODE 的变量控制,默认值是 UNRESTRICTED。一旦设为 STRICT,引擎会在任何流程代码运行之前给 Node 的 DNS 查询和 socket 连接打补丁,并拒绝一切地址属于回环、RFC1918 私有网段、链路本地或云元数据的连接。它会抛出一个名为 SSRFBlockedError 的错误。
跑在你自己网络里的验证码识别程序,正好就是这道防护要拦的那种目标。127.0.0.1 和像 192.168.1.40 这样的局域网地址都在名单上。这是防护在正常工作,不是 bug,而 Activepieces 也给了文档化的例外做法:把识别程序的地址填进 AP_SSRF_ALLOW_LIST,它接受用逗号分隔的 IP 和 CIDR 网段,对流程代码和服务器自身的出站请求同样生效。改完之后要重启服务器。
# On a self-hosted Activepieces with AP_NETWORK_MODE=STRICT, # name the solver machine or its subnet so flows can reach it. AP_SSRF_ALLOW_LIST=192.168.1.40,10.0.5.0/24
除了这道防护之外,流程首先得能访问到那台机器。CapSkip 为此提供了两种连接模式。Local 绑定 127.0.0.1,只服务本机。Server 绑定你的网络地址或公网 IP,这样另一台机器、一个容器宿主机或者一个托管平台就能通过 API 访问同一台 Windows 机器。两者的设置都在 连接设置,而 Server 模式只改变识别程序监听在哪个地址上。硬件还是你自己的,识别也依然不计量。
| Activepieces 运行在哪里 | 用哪种模式,还要做什么 |
|---|---|
| 自托管在与 CapSkip 相同的那台 Windows 机器上 | Local 模式,host 保持 127.0.0.1。如果网络模式是 STRICT,就把它加进白名单 |
| 自托管在 Docker 里,或者你网络中的另一台机器上 | Server 模式,填识别程序的局域网地址。这个地址同样要加进白名单 |
| Activepieces Cloud | Server 模式,配静态公网 IP 加一条防火墙规则。SSRF 防护照样在跑,只是不会拦截,因为公网地址不在它的拦截名单上 |
第 4 步:超时与重试
有三个数字决定一次慢速识别能不能撑下来,其中只有一个是你在流程里能设的。
| 哪个限制 | 值 | 它为什么会影响一次识别 |
|---|---|---|
| 整次流程运行和任何单个操作,各自独立计入上限 | 各 10 分钟,都来自 AP_FLOW_TIMEOUT_SECONDS 这一个变量 | 很宽裕,因为延时的时间不计入流程运行超时 |
| 同步 webhook 响应超时 | 30 秒,由 AP_WEBHOOK_TIMEOUT_SECONDS 设定 | 陷阱就在这里。见下面一段 |
| Retry on Failure,按步骤设置 | 共 4 次尝试,等待分别是 4 秒、8 秒和 16 秒 | 能兜住正在重启的识别程序,兜不住只是慢的那种 |
真正会咬人的是 webhook 那个数字。以 sync 结尾的 webhook URL 会一直保持 HTTP 连接不断开,并把流程的结果作为响应返回,而它在 30 秒后就放弃。一次 reCAPTCHA v2 识别未必能稳定在 30 秒内完成,所以同步触发流程并等着拿回 token 的调用方会收到 HTTP 408,而流程还在后面继续跑。改成异步触发流程,让它把 token 发到你需要的地方,或者把工作拆开,让同步的那一半永远不用等识别。
Retry on Failure 值得给提交步骤打开,不要给读取步骤打开。它的退避是以 2 秒为基数的指数退避,所以 4 次尝试之间的等待大致是 4 秒、8 秒和 16 秒。对一次被拒绝的连接来说这是对的。对你已经取回的 token 来说这是错的,原因就是上面那条只能读一次的规则。
在自托管实例上,一个步骤搞定全部
如果你的管理员用的是无沙箱或内核命名空间沙箱,上面那套流程可以缩成一个 Code 步骤,因为 SDK 会替你轮询。在 npm 对话框里加上 capskip,然后写这个步骤。Code 步骤是 TypeScript,运行前会先打包,所以普通的 import 就能用。
// npm install capskip - add it in the step's package dialog.
import { CapSkip } from 'capskip';
export const code = async (inputs) => {
// host is the solver machine. Keep 127.0.0.1 only when
// Activepieces runs on the same Windows box as CapSkip.
const solver = new CapSkip({
host: inputs.capskipHost,
port: 8080,
apiKey: inputs.capskipKey,
});
const result = await solver.recaptcha(inputs.sitekey, inputs.pageUrl);
// Return the token, not the whole result. The next step
// submits it, and run logs keep whatever you return.
return { token: result.code };
};把 capskipKey 作为步骤输入传进去,里面放项目变量的引用,这样密钥在运行时才解析,永远不会出现在源码里。SDK 从 250 毫秒开始轮询并逐步退避,而不是按固定间隔休眠,这就是为什么这个版本通常比基于 Delay 的流程更快返回。它对 reCAPTCHA、Turnstile 和极验的上限是 300 秒,远在 10 分钟的操作超时之内。
在紧接着的下一个步骤里就把 token 提交出去。一个 reCAPTCHA token 大约有两分钟有效期,所以一个先识别、再卡在审批步骤上等待、然后才提交的流程,会栽在一个生成时完全有效的 token 上。这种失败情形的完整说明见 reCAPTCHA token 过期.
常见错误及其含义
| 你所看到的 | 原因 | 修复 |
|---|---|---|
| 运行日志里出现 SSRFBlockedError | 网络模式是 STRICT,而识别程序在私有地址上 | 把该地址加进 AP_SSRF_ALLOW_LIST,然后重启服务器 |
| 流程运行时提示找不到 capskip 模块 | 沙箱模式在构建时丢掉了这个依赖 | 把这个步骤改写成 HTTP piece 调用,或者自托管在允许使用包的模式下 |
| 8080 端口连接被拒绝 | CapSkip 绑定在回环地址上,而 worker 在别处 | 切换到 Server 模式,并使用识别程序的网络地址 |
| 每次读取都返回 CAPCHA_NOT_READY | 延时比识别耗时还短 | 调高第一个 Delay,或者再加一次延时和读取 |
| 对同一个 id 的第二次读取失败 | CapSkip 的结果只能读取一次 | 把 token 存进步骤输出,永远不要再去读那个 id |
| 响应里出现 ERROR_WRONG_USER_KEY | 项目变量解析成了空字符串 | 检查变量名,包括大小写是否完全一致 |
| 同步 webhook 返回 HTTP 408 | 识别耗时超过了 30 秒的 webhook 超时 | 改为异步触发,或者把识别挪出同步路径 |
| 有效的 token 被目标站点拒绝 | 它在识别步骤和提交步骤之间过期了 | 在下一个步骤就提交,中间不要夹审批或延时 |
常见问题
我能从 Activepieces Cloud 使用 CapSkip 吗?
可以,用 Server 模式。worker 在 Activepieces 的基础设施上而不是你的,所以识别程序必须监听在他们能访问到的地址上:一个公网 IP,最好是静态的,再配一条允许他们流量进入的防火墙规则。识别程序本身什么都不用改,改的只是它监听在哪里。在他们的云上做不到的事情,是在 Code 步骤里用 Node SDK,因为那种模式没有 npm,所以要把流程搭在 HTTP piece 上。
为什么添加 npm 包看起来是成功的?
因为那个对话框是界面上的功能,而过滤发生在服务端。对话框会在 npm registry 里解析这个包并把它记录下来。构建时服务器会先问执行模式是否允许使用包,如果不允许,就在安装前换成一份空依赖集合。步骤会照常编译并部署,没有任何警告。你只有在运行时才会发现,那时 import 解析到的是空的。
流程应该一直循环到 token 到达吗?
通常不该。Loop on Items 会把配置好的整个列表跑完,所以你配了多少次迭代就要付出多少次代价,而且每次迭代都会去重读一个只能读一次的 id。用一个长度合适的延时加一次读取,既更便宜也更正确,再加一次延时和读取作为兜底也很合适。这里的长延时格外便宜,因为超过 10 秒的延时会把运行挂起,而不是占着一个 worker,挂起的时间也不计入运行超时。
这和在 n8n、Make.com 或 Zapier 里做有什么不同?
在这四个工具里,请求本身完全一样。不同的是每个工具在请求前面摆了什么障碍。
- 对 n8n 来说,障碍是容器网络,而 n8n 工作流指南 把它讲透了。
- 对 Make.com 来说,障碍是它的 HTTP 模块索要的那张证书,对此 Make.com 操作演示 有完整说明。
- 对 Zapier 来说,障碍是 Code 步骤的运行时长上限,详见 Zapier 指南.
Activepieces 则加了两道自己的障碍:一种决定 npm 到底存不存在的沙箱模式,以及一道会直接拒绝私有地址的 SSRF 防护。
简短版结论
把识别搭在 HTTP piece 上,因为它在每种沙箱模式下都能用,而 Code 步骤那条路不行。提交到提交接口,把延时设到 10 秒以上,让这次运行被挂起而不是占着一个 worker,然后把结果读一次并保存下来。密钥放进项目变量。如果实例是自托管而且网络模式很严格,就把识别程序加进 SSRF 白名单;如果 Activepieces 跑在识别程序本机以外的任何地方,就把 CapSkip 切到 Server 模式。永远不要在同步 webhook 上等一次识别。
- reCAPTCHA v2 复选框本身的说明见 reCAPTCHA v2 识别页面.
- Python、Node.js、PHP 和 C# 中等价的一次调用版本见 验证码识别 SDK 页面.
在把这个流程设成每隔几分钟运行一次之前,有一点值得掂量:CapSkip 是一个 无限量验证码识别工具 ,运行在你已经拥有的硬件上,所以不停触发的流程和偶尔触发的流程,花费完全相同。
