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

activepieces captcha - How to Solve CAPTCHAs in 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_PROCESSnpm 包可用Node SDK 可用,而且它会替你轮询
无沙箱,UNSANDBOXEDnpm 包可用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 CloudServer 模式,配静态公网 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 上等一次识别。

在把这个流程设成每隔几分钟运行一次之前,有一点值得掂量:CapSkip 是一个 无限量验证码识别工具 ,运行在你已经拥有的硬件上,所以不停触发的流程和偶尔触发的流程,花费完全相同。