如何在 Pipedream 代码步骤中识别验证码(Node.js)

在 Pipedream 里识别验证码比在 Zapier 或 Make.com 里做同样的事更容易,因为代码步骤是一个真正的 Node.js v20 运行时,支持 npm 导入。你什么都不用装,写普通的 JavaScript 即可,CapSkip SDK 的表现和在你笔记本上一模一样。有两点不同,而且都和代码在哪里运行有关。Pipedream 在它自己的云上执行,所以识别程序必须能从公网访问到。另外,一次工作流执行的时间上限比一次 reCAPTCHA 识别还短,这决定了你要写一个步骤还是两个。
你需要什么
- 一个带代码步骤的 Pipedream 工作流。下文使用的运行时是 Node.js。再往下还有 Python 版本。
- CapSkip 以 Server 模式运行在一台能从公网访问、并拥有静态公网 IP 的机器上。
- 你要自动化的站点的 sitekey 和页面 URL。
- 两个 Pipedream 环境变量,分别保存识别程序的地址和密钥。
为什么回环地址在这里行不通
Pipedream 工作流运行在 Pipedream 自己的基础设施上,位于 AWS us-east-1 网络中。代码步骤内部发往 127.0.0.1 的请求会解析到该步骤所在的容器,而不是你桌上的机器。那里没有任何东西在监听,SDK 会抛出 NetworkException。
CapSkip 有两种连接模式,答案是第二种。Local 绑定 127.0.0.1,只服务本机,当自动化和识别程序在同一台机器上时这样最合适。Server 绑定你的网络地址或公网 IP,这样托管平台就能通过 API 访问同一台 Windows 机器。建议使用静态公网 IP,因为会轮换的家宽地址会在凌晨三点悄无声息地把工作流搞挂。两种模式的设置都在 连接设置.
Server 模式只改变识别程序运行的位置,其他什么都不变。它仍然是你自己的硬件,仍然不计量,所以每月触发一万次的工作流和触发十次的花费完全相同。
第 1 步:把地址放进环境变量
不要把公网 IP 硬编码在步骤里。Pipedream 有工作区环境变量,代码步骤可以从普通的进程环境中读取它们。创建两个。
| 变量名 | 值 |
|---|---|
| 名为 CAPSKIP_HOST 的那个 | 识别程序所在机器的公网 IP,不带协议头,不带端口 |
| 名为 CAPSKIP_API_KEY 的那个 | 你在 CapSkip 应用中为这个工作流生成的密钥 |
给这个工作流单独的密钥,不要共用。当某个密钥泄漏到共享工作区而需要吊销时,不该把你其余的自动化一起拖下水。
第 2 步:一个代码步骤完成整个识别
只要你导入,Pipedream 就会立刻安装对应的 npm 包,所以没有安装步骤,也没有包描述文件。导入说明符同时也是你锁定版本的地方,对于无人值守运行的东西,锁版本很值得做。
// npm install capskip - Pipedream installs it from this import.
// The package is CommonJS, so take the default and destructure.
import capskip from "capskip";
const { CapSkip } = capskip;
export default defineComponent({
async run({ steps, $ }) {
const solver = new CapSkip({
host: process.env.CAPSKIP_HOST,
port: 8080,
apiKey: process.env.CAPSKIP_API_KEY,
});
const result = await solver.recaptcha(
"YOUR_SITEKEY",
"https://example.com/page-with-recaptcha"
);
return result.code; // the token, for the next step
},
});这就是全部了。每种 reCAPTCHA 变体都是同一个方法,只是选项对象不同:invisible 设为 1、enterprise 设为 1,或者 version 设为 v3 并带上 action 名称。Turnstile 和极验有各自的方法,形态相同,完整参数列表见 CapSkip API 文档.
步骤返回的任何东西都会进入工作流的导出,因此后面的步骤可以用该步骤自己的名称读到 token。如果你更想给它加个标签,就用导出辅助方法。
// A named export reads better downstream than a bare return.
$.export("token", result.code);
// The next step then reads steps.solve_captcha.token执行超时,以及一个步骤何时不再够用
下面这个限制决定了其他一切。Pipedream 的一次执行,对 HTTP 和邮件触发器默认时间上限是 30 秒,对定时触发器是 60 秒。你可以在工作流设置里调高,免费档最高 300 秒,付费档最高 750 秒。
一次 reCAPTCHA v2 识别通常远在 30 秒以内完成,但通常不等于总是,而 SDK 自己的上限是 recaptchaTimeout,为 300 秒。所以上面那个单步版本的可靠程度,正好等于你的超时设置。如果工作流上限低于识别耗时,执行会在轮询到一半时被杀掉,你会得到一次失败运行,里面没有任何有用的信息。
| 你的情况 | 该怎么做 |
|---|---|
| 用量低,而且你可以把执行上限调到 300 秒 | 保留单步版本。在工作流设置里调高超时 |
| 用量高,或者你在为执行时长付费 | 拆开它,使用下面介绍的 rerun 辅助方法 |
| 耗时更长的 Turnstile 挑战页或极验 | 拆开它。这两类最容易超出较短的时间上限 |
第 3 步:用 rerun 辅助方法拆开它
Pipedream 有一个大多数自动化平台都没有的轮询原语。flow rerun 辅助方法会结束当前步骤,等待一段时间,然后带着你交给它的一小段状态再次运行同一个步骤。等待期间工作流并不在执行,所以慢速识别不会让你付出代价,也不会撞上时间上限。
有三点让它成立。运行计数从 1 开始,每次 rerun 递增。你传入的上下文对象在下一轮里可以读到。超过重试上限时,工作流会继续走到下一个步骤,而不是失败,所以如果你不想要这个行为,就主动抛出异常。
// No SDK here. The raw endpoints suit a step that exits
// between polls, because nothing has to stay in memory.
const MAX_RETRIES = 20;
const DELAY = 15000; // 15s, the recommended first wait for v2
export default defineComponent({
async run({ steps, $ }) {
const { run } = $.context;
const base = `http://${process.env.CAPSKIP_HOST}:8080`;
const key = process.env.CAPSKIP_API_KEY;
if (run.runs === 1) {
const params = new URLSearchParams({
key,
method: "userrecaptcha",
googlekey: "YOUR_SITEKEY",
pageurl: "https://example.com/page-with-recaptcha",
json: "1",
});
const submitted = await fetch(`${base}/in.php?${params}`);
const { request: id } = await submitted.json();
// The id survives into the next run through the context.
return $.flow.rerun(DELAY, { id }, MAX_RETRIES);
}
const { id } = $.context.run.context;
const polled = await fetch(
`${base}/res.php?key=${key}&action=get&id=${id}&json=1`
);
const data = await polled.json();
if (data.request !== "CAPCHA_NOT_READY") {
return data.request; // the token
}
if (run.runs === MAX_RETRIES + 1) {
throw new Error("Solve did not finish in time");
}
return $.flow.rerun(DELAY, { id }, MAX_RETRIES);
},
});那个未就绪响应的拼写很容易让人栽跟头。它是 CAPCHA_NOT_READY,少了一个 T,而且它不是错误:它表示答案还没算完,你应该再轮询一次。把它当成失败是手写轮询循环里最常见的 bug,这里还有 一篇关于 CAPCHA_NOT_READY 响应的完整说明.
关于那个接口还有一点。结果只能读取一次。如果你先把响应体打印出来,之后又在别的步骤里再读一次,第二次读到的是空的,看起来就像识别失败了。
拿到 token 立刻发出去
一个 reCAPTCHA token 大约有两分钟有效期。在工作流里,这比听上去更容易浪费掉,因为延时步骤、缓慢的 HTTP 调用,或者等待过久的一次 rerun,都在吃同一份预算。把提交 token 的步骤紧接在产生它的步骤之后,不要把 token 攒起来留着以后用。完整说明见 reCAPTCHA token 过期指南.
Python 版本
Pipedream 也能运行 Python 3.12 代码步骤,并以同样的方式根据你的导入安装 pip 包。SDK 不会自己读取环境变量,所以下面这个步骤会读取 CAPSKIP_HOST、CAPSKIP_PORT 和 CAPSKIP_API_KEY,并把它们传给构造函数。
# pip install capskip - Pipedream installs it from this import
import os
from capskip import CapSkip
def handler(pd: "pipedream"):
# Your Pipedream environment variables. The SDK does not read
# them by itself, so pass them to the constructor.
solver = CapSkip(
host=os.environ["CAPSKIP_HOST"],
port=int(os.environ["CAPSKIP_PORT"]),
apiKey=os.environ["CAPSKIP_API_KEY"],
)
page_url = pd.steps["trigger"]["event"]["body"]["url"]
result = solver.recaptcha(sitekey="YOUR_SITEKEY", url=page_url)
# Downstream steps read pd.steps["solve"]["token"]
return {"token": result["code"]}如果走这条路,除了另外两个变量之外,还要把 CAPSKIP_PORT 设为 8080。定下来之前要知道一个限制:rerun 和 delay 辅助方法只在 Node.js 下有文档,所以 Python 步骤只能是一次性的版本。如果你需要拆分轮询的形态,就用 Node 来写那一个步骤。
把你刚打开的端口锁紧
Server 模式会在公网上放一个监听器,所以把它当成任何其他对外暴露的服务来对待。有三件事值得第一天就做。
- 在 CapSkip 应用中打开 API 密钥校验,并给这个工作流单独的密钥。不开的话,任何字符串都会被当作密钥接受。
- 把识别程序放在防火墙规则之后,而不是把 8080 对所有人敞开。
- 想清楚这条规则怎么写。Pipedream 普通的出站流量来自标准的 AWS us-east-1 地址段,范围太宽,加进白名单没有实际意义。如果你需要一条精确的规则,Pipedream 提供 VPC,可以为每个工作区分配专用的静态出站 IP,那才是你该加白名单的地址。
常见错误及其含义
| 你所看到的 | 原因 | 修复 |
|---|---|---|
| NetworkException,或者 8080 端口连接被拒绝 | 识别程序处于 Local 模式,或者 host 变量写错了 | 切换到 Server 模式,并把 host 变量设为公网 IP |
| 识别进行到一半时执行被杀掉 | 工作流的时间上限短于识别耗时 | 在工作流设置里调高,或者改用 rerun 版本 |
| 步骤把表示未就绪的响应原文当作结果返回了 | 轮询分支把 CAPCHA_NOT_READY 当成了答案 | 显式判断它,然后 rerun,而不是把它返回 |
| 裸接口返回 ERROR_WRONG_USER_KEY | 密钥变量是空的,所以发出去的是空字符串 | 检查环境变量名,包括大小写 |
| 第二次读取时 token 是空的 | 结果只能读取一次 | 读一次,存进变量,然后把变量传下去 |
| 有效的 token 被目标站点拒绝 | 它在识别和提交之间过期了 | 把提交步骤直接挪到识别步骤之后 |
| Cannot use import statement outside a module | 这个步骤混用了 import 和 require | 每个步骤只用一种写法。代码步骤是 ES 模块 |
常见问题
我能在不暴露任何东西的情况下从 Pipedream 使用 CapSkip 吗?
不能直接做到,因为 Pipedream 的算力是 Pipedream 的。总得有一端接受入站连接。开启密钥校验并配上防火墙规则的 Server 模式,就是最直接的答案。如果你的规定完全禁止开放端口,替代做法是把验证码这部分工作留在你自己控制的机器上,让 Pipedream 去调用那台机器自己的接口,但这只是把暴露面挪了个位置,并没有消除它。
一次 rerun 算作单独的一次执行吗?
步骤会再跑一遍,所以代码执行不止一次,这正是计数器存在的原因。这个辅助方法的意义在于等待期间工作流不会被一直挂着,因此慢速识别不会把单次执行推过时间上限。如果计费对你重要,去看你所在方案的计费口径,但无论如何超时问题都已经解决了。
我该用 SDK 还是裸接口?
当一个步骤就能完成全部工作时用 SDK,因为它会处理轮询,从 250 毫秒开始退避而不是按固定间隔休眠,还会给你带类型的错误。当你把流程拆到多次 rerun 里时用裸接口,因为步骤在两次轮询之间就退出了,内存里没有客户端来保住那个任务。两者访问的是同一个端口上的同一个服务。
这和在 n8n 或 Zapier 里做有什么不同?
主要是运行时不同。Pipedream 给你的是真正的 Node.js,支持 npm 导入,所以 SDK 可以直接塞进去,而 rerun 辅助方法能干净地应付慢速识别。Zapier 的代码步骤不能安装包,所以 Zapier 验证码指南 整个都是围绕它的超时来设计的。Make.com 根本没有代码步骤,这就是为什么 Make.com 操作演示 是用 HTTP 模块拼出来的。n8n 可以自托管在识别程序旁边,所以 n8n 指南 往往可以继续用 Local 模式。
简短版结论
把 CapSkip 设为 Server 模式,把它的地址和密钥放在 Pipedream 环境变量里,然后把 SDK 直接导入 Node.js 代码步骤。如果工作流的时间上限明显高于你最慢的一次识别,一个步骤就是全部集成。如果不是,就把步骤拆开:提交到裸接口,把任务 id 交给 rerun 辅助方法,在重新进入时轮询。拿到 token 后立刻提交,因为它大约两分钟就会过期。
Node.js 这一侧的内容见 Node.js 验证码识别页面,复选框挑战见 reCAPTCHA v2 识别页面,以及 Python、PHP 和 C# 中的等价调用见 验证码识别 SDK 页面.
在把它接进全天运行的东西之前,有件事值得知道:CapSkip 提供的是 验证码绕过 ,运行在你已经拥有的硬件上,所以一个持续不断触发的工作流和一个偶尔触发的工作流,花费完全相同。
