本文介绍如何使用 Node.js 和 Playwright 自动化测试工具,结合 2Captcha 验证码识别服务,解决 B 站 Geetest 验证码导致的自动登录难题。通过拦截接口获取参数、调用 2Captcha API 识别验证码,并模拟用户操作完成登录流程,提供完整的代码实现与步骤解析。
Node.js Playwright 结合 2Captcha 实现自动登录
需求背景
在日常工作中,为了提升效率,我们常编写脚本自动执行任务。对于需要登录的网站,自动登录功能是脚本不可或缺的一部分。
然而,登录时常见的验证码旨在防止机器和自动化脚本操作。因此,如何让脚本自动识别验证码并完成登录成为关键问题。
本文将以 B 站为例,详细讲解如何解决自动登录脚本中的验证码识别问题。
前期探索
首先,我们需要体验目标网站的登录方式,了解其验证码类型。
访问 B 站首页,打开浏览器控制台并点击登录,会弹出小型登录框。输入账号密码后,通常会弹出验证码框,此时猜测验证码接口已被请求。
由于验证码的英文是 captcha ,我们在 network 面板里搜 captcha

发现了一个和验证码相关的接口
https://passport.bilibili.com/x/passport-login/captcha查看接口返回结果,确实包含有用信息,我们发现验证码类型是 geetest 。
{
"code": 0,
"message": "0",
"ttl": 1,
"data": {
"type": "geetest",
"token": "b416c387953540608bb5da384b4e372b",
"geetest": {
"challenge": "aeb4653fb336f5dcd63baecb0d51a1f3",
"gt": "ac597a4506fee079629df5d8b66dd4fe"
},
"tencent": {
"appid": ""
}
}
}通过搜索发现,B 站使用的验证码服务是 geetest 提供的,国内许多网站均采用此服务。geetest 验证码的特点是移动拼图、按顺序选择文字或数字。
接下来,我们将寻找识别 geetest 验证码的方法。
经过调研,市面上效果较好的方案多为 OCR 服务商。对比后认为 2Captcha 服务表现优异,解码速度快、服务器连接稳定、支持多语言 API 且价格公道,因此决定试用 2Captcha 。
2Captcha 官网
下文将展示如何使用 Nodejs + Playwright + 2Captcha 来解决 B 站的登录验证码问题。
若使用其他语言和框架,如
Python+Selenium,也可参考此教程,解决思路相同。
解决方案
1. 如何识别验证码
首先仔细阅读官方文档 2Captcha API Geetest,解决方案描述详尽。简单来说:
- 通过拦截网站接口,获取
gt、challenge这两个校验码参数,请求http://2captcha.com/in.php,得到验证码ID - 隔一段时间再请求
http://2captcha.com/res.php,得到校验成功的标识challenge、validate、seccode
1. 如何应用验证结果
拿到最关键的 validate 之后,模拟用户填写账号密码登录,拦截验证码请求接口的返回参数,替换为校验成功的参数,随即触发登录接口。
接下来,我们分析详细步骤。
环境准备
首先搭建脚本执行环境。
我们使用 Node.js + Playwright 来编写脚本。
- 确保电脑本地已安装 Nodejs
- 新建空项目,安装
Playwright
mkdir bypass-captcha
cd bypass-captcha
npm init
npm i -D playwright我们采用
Playwright的库模式,详细文档:Playwright
在项目根目录新建脚本文件 captcha.js,填入以下内容,命令行运行node captcha.js来简单测试项目是否正常启动
const {
chromium
} = require("playwright");
(async () => {
const browser = await chromium.launch({
headless: false,
});
const page = await browser.newPage();
await page.goto("https://www.bilibili.com/");
await browser.close();
})();正常情况下会弹出一个谷歌浏览器界面,显示 B 站首页,随后浏览器自动关闭。
请求 in.php 接口
首先整理请求http://2captcha.com/in.php接口所需的参数,查看参数列表,关注必传参数
| 参数 | 类型 | 必选 | 描述 |
|---|---|---|---|
| key | String | 是 | 您的 API key |
| method | String | 是 | geetest - 定义您正在发送的是 Geetest 的验证码 |
| gt | String | 是 | 在目标网站上找到的 gt 参数 |
| challenge | String | 是 | 在目标网站上找到的 challenge 参数 |
| api_server | String | 否 | 在目标网站上找到的 api_server 参数 |
| pageurl | String | 是 | 您看到 Geetest 验证码时所在网页的完整 URL |
| header_acao | Integer 默认: 0 | 否 | 0 - 禁用 1 - 启用。 如果启用 in.php 将在响应中包含 Access-Control-Allow-Origin:* 标头。 用于 Web 应用程序中的跨域 AJAX 请求。 res.php 也支持 |
| pingback | String | 否 | 解决验证码时将发送的 pingback(回调)响应的 URL。 URL 应该在服务器上注册。 更多信息在这里 |
| json | Integer 默认: 0 | 否 | 0 - 服务器将以纯文本形式发送响应 1 - 告诉服务器以 JSON 格式发送响应 |
| soft_id | Integer | 否 | 软件开发人员的 ID。 将他们的软件与 2Captcha 集成的开发人员将获得奖励:软件用户支出的 10%。 |
| proxy | String | 否 | 格式: login:password@123.123.123.123:3128 您可以找到更多关于代理的信息这里。 |
| proxytype | String | 否 | 代理类型: HTTP, HTTPS, SOCKS4, SOCKS5. |
| userAgent | String | 否 | 您的 userAgent 将传递给我们的工作人员并用于解决验证码。 |
key是需要在 2Captcha 官网注册账户后,后台面板的账户设置中就有一个API key,当然要起让 key 生效的话还需要充值一定金额method是一个固定值geetestgt和challenge之前已经在网站登录页面的接口中看到了。不过这里有个注意点,gt是每个网站只有一个值,B 站这里是固定的ac597a4506fee079629df5d8b66dd4fe,但是challenge是一个动态值,每次 API 请求都会获得一个新的challenge值。在页面上加载验证码后,challenge值就会失效。 所以要在网站登录页加载的时候监听https://passport.bilibili.com/x/passport-login/captcha这个请求,每次重新识别出新的challenge值。下面会讲解如何监听到。pageurl就是登录页的地址https://www.bilibili.com/
于是我们可以得到类似这样一个请求接口
http://2captcha.com/in.php?key=1abc234de56fab7c89012d34e56fa7b8&method=geetest>=ac597a4506fee079629df5d8b66dd4fe&challenge=12345678abc90123d45678ef90123a456b&pageurl=https://www.bilibili.com/1. 接下来就解决每次进首页获取新的 challenge 值
模拟用户点击登录的流程
- 先启动谷歌浏览器,打开 B 站首页
- 点击顶部的登录按钮,会弹出登录框
- 此时验证码接口已发出,在此监听验证码接口返回的参数,即可截获到
gt和challenge的值
const {
chromium
} = require("playwright");
(async () => {
// 选择Chrome浏览器,设置headless: false 能看到浏览器界面
const browser = await chromium.launch({
headless: false,
});
const page = await browser.newPage();
// 打开B站
await page.goto("https://www.bilibili.com/");
const [response] = await Promise.all([
// 请求验证码接口
page.waitForResponse(
(response) =>
response.url().includes("/x/passport-login/captcha") &&
response.status() === 200
),
// 点击顶部的登录按钮
page.click(".header-login-entry"),
]);
// 获取到接口返回信息
const responseJson = await response.body();
// 解析出 gt 和 challenge
const json = JSON.parse(responseJson);
const gt = json.data.geetest.gt;
const challenge = json.data.geetest.challenge;
console.log("得到 gt", gt, "challenge", challenge);
// 暂停5秒,防止浏览器关闭太快,来不及看到效果
sleep(5000);
// 关闭浏览器
await browser.close();
})();
/**
* 模拟sleep功能,延迟一定时间,单位毫秒
* Delay for a number of milliseconds
*/
function sleep(delay) {
var start = new Date().getTime();
while (new Date().getTime() < start + delay);
}1. 使用request库来单独请求in.php接口
先安装 request
npm i request现在可以开始正式的请求 http://2captcha.com/in.php 接口了
// 请求 in.php 接口
const inData = {
key: API_KEY,
method: METHOD,
gt: gt,
challenge: challenge,
pageurl: PAGE_URL,
json: 1,
};
request.post(
"http://2captcha.com/in.php", {
json: inData
},
function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log("response", body);
}
}
);正常情况下,此时会返回验证码 ID ,比如 {"status":1,"request":"2122988149"} ,取 request 字段即可。
如果接口返回代码
ERROR_ZERO_BALANCE,表明您的账户余额不足,需要充值,我这里充值了最低额度用于演示,大家根据自己需要适当体验下。
扩展学习
1. 为提升安全性,我们将 API Key 写在环境变量文件中来引用。
在根目录新建一个环境变量文件.env,写入API Key的值
# .env文件
API_KEY="d34y92u74en96yu6530t5p2i2oe3oqy9"1. 然后安装dotenv库,用来获取到环境变量
npm i dotenv1. 在 js 脚本中使用
require("dotenv").config();这样通过 process.env.API_KEY 就能取到 .env 中的变量了,通常 .env 文件不上传到代码仓库,保证个人信息的安全性。
如果不想把信息写到文件中的同时确保安全性,也可以直接在控制台输入传入 Node.js 环境变量,比如
API_KEY=d34y92u74en96yu6530t5p2i2oe3oqy9 node captcha.js请求 res.php 接口请求接口之前,我们也整理下所需参数
| GET 参数 | 类型 | 必选 | 描述 |
|---|---|---|---|
| key | String | 是 | 您的 API key |
| action | String | 是 | get - 得到您的验证码的答案 |
| id | Integer | 是 | in.php 返回的验证码 ID |
| json | Integer 默认: 1 | 否 | 服务器将始终以 JSON 格式返回 Geetest 验证码的响应。 |
key就是API_KEY,上一个接口也用到了action就是固定值getid是刚刚in.php返回的验证码ID
上一个请求 20 秒之后,再请求 http://2captcha.com/res.php 接口获取验证结果
request.get(
`http://2captcha.com/res.php?key=${API_KEY}&action=get&id=${ID}&json=1`,
function(error, response, body) {
if (!error && response.statusCode == 200) {
const data = JSON.parse(body);
if (data.status == 1) {
console.log(data.request);
}
}
}
);接口会返回三个值 challenge 、 validate 和 seccode ,每一个参数都是一个字符串
{
"geetest_challenge": "aeb4653fb336f5dcd63baecb0d51a1f3",
"geetest_validate": "9f36e8f3a928a7d382dad8f6c1b10429",
"geetest_seccode": "9f36e8f3a928a7d382dad8f6c1b10429|jordan"
}其中 challenge 就是前面我们拦截到的参数, validate 是校验结果标识, seccode 内容和 validate 基本一致,只多了一个单词。我们需要将 validate 存储下来备用。
这里有时候会碰到验证码无法校验通过的情况,可以多尝试几次,或者联系 2Captcha 官网排查问题
到这里,验证码校验结果的信息已经获取完整,接下来就是拿校验结果去登录了。
登录
1. 先来研究下正常用户点击验证码校验成功后的登录流程

我们发现了三个接口
https://api.geetest.com/ajax.php:验证码接口,用于生成验证码和校验验证码是否通过。校验接口返回数据中的 validate 字段就是 2Captcha 服务获取到的 geetest_validate。

https://passport.bilibili.com/x/passport-login/web/key?_=1649087831803:密码加密接口,用于获取 hash 和公钥

https://passport.bilibili.com/x/passport-login/web/login:登录接口,入参包括账号、密码、token、challenge、validate和 seccode等

分析这几个接口,可以得出两个登录方案。
- 第一个方案,在
Node.js环境下请求加密接口和登录接口,获取用户 Cookie 信息,后续用户登录直接携带 Cookie 信息即可。这个方案的难点是需要单独处理密码加密,对新手不太友好。 - 第二个方案,用
Playwright模拟用户填写账号密码登录,随机点击验证码触发登录,拦截验证码接口的返回参数,替换为校验成功的标识,随即触发登录接口。
我们采用第二种方案。
不过也遇到一个坑,就是 Node.js 环境下,验证码图片加载不出来,后面发现验证码接口 https://api.geetest.com/ajax.php 同时负责拉取验证码图片和校验验证码,我们直接拦截拉取验证码图片时的请求,替换校验结果就能触发登录了,不需要等图片验证码出来。这个细节很关键。
到此这篇关于Nodejs Playwright 2Captcha 验证码识别实现自动登录功能的文章就介绍到这了,更多相关Nodejs Playwright 2Captcha 验证码识别内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!