Nodejs Playwright 2Captcha 驗證碼識別實現(xiàn)自動登陸功能
Nodejs Playwright 2Captcha 驗證碼識別實現(xiàn)自動登陸
需求
日常工作當中,為了提高工作效率,我們可能會寫腳本來自動執(zhí)行任務(wù)。有些網(wǎng)站因為需要用戶登陸,所以腳本的自動登陸功能必不可少。
不過我們在登陸網(wǎng)站的時候經(jīng)常會出現(xiàn)驗證碼,驗證碼的目的就是為了防止機器登陸、自動化腳本操作,那么有沒有辦法讓腳本能自動識別驗證碼實現(xiàn)登陸呢?
接下來我以 B 站為例給大家講解下,如何解決自動登陸腳本中最關(guān)鍵的驗證碼問題。
探索
首先需要體驗下這個網(wǎng)站的登陸方式,了解下它的驗證碼類型。
打開 B 站 https://www.bilibili.com/ ,打開控制臺,點擊登陸,這時候會彈出中間小的登陸框,通常輸入完賬號和密碼,就會彈出驗證碼框了,我們猜測驗證碼的接口此時已經(jīng)請求了。
由于驗證碼的英文是 captcha ,我們在 network 面板里搜 captcha

發(fā)現(xiàn)了一個和驗證碼相關(guān)的接口
https://passport.bilibili.com/x/passport-login/captcha
點開看接口返回結(jié)果,果然有一些有用的信息,我們發(fā)現(xiàn)驗證碼類型是 geetest 。
{
"code": 0,
"message": "0",
"ttl": 1,
"data": {
"type": "geetest",
"token": "b416c387953540608bb5da384b4e372b",
"geetest": {
"challenge": "aeb4653fb336f5dcd63baecb0d51a1f3",
"gt": "ac597a4506fee079629df5d8b66dd4fe"
},
"tencent": {
"appid": ""
}
}
}通過搜索發(fā)現(xiàn)了 B 站使用的驗證碼服務(wù)是 geetest 提供的,國內(nèi)有很多網(wǎng)站都用的這個服務(wù), geetest 驗證碼的特點是移動拼圖、按順序選擇文字或者數(shù)字。
那么接下來,就來尋找可以識別 geetest 驗證碼的辦法。
小編了解了市面上提供的驗證碼解決方案,效果比較好的基本都是 OCR 服務(wù)商,對比之后發(fā)現(xiàn) 2Captcha 的服務(wù)非常好,解碼速度快、服務(wù)器連接穩(wěn)定、支持多種語言 API、價格公道,小編決定試用下 2Captcha 。
接下來就展示使用 Nodejs + Playwright + 2Captcha 來解決 B 站的登陸驗證碼問題。
如果想用其他語言和框架,比如
Python+Selenium,也可以參照這個教程,解決問題的思路都是一樣的。
解決
1.如何識別驗證碼
先仔細閱讀官方文檔 2Captcha API Geetest,解決方案寫的很詳細,簡單來說
- 通過攔截網(wǎng)站接口,獲取
gt、challenge這兩個校驗碼參數(shù),請求http://2captcha.com/in.php,得到驗證碼ID - 隔一段時間再請求
http://2captcha.com/res.php,得到校驗成功的標識challenge、validate、seccode
1.如何應(yīng)用驗證結(jié)果
拿到最關(guān)鍵的 validate 之后,模擬用戶填寫賬號密碼登陸,攔截驗證碼請求接口的返回參數(shù),替換為校驗成功的參數(shù),隨即觸發(fā)登陸接口。
接下來,我們分析下詳細步驟
環(huán)境準備
我們先搭建一下腳本執(zhí)行環(huán)境。
我們使用 Node.js + Playwright 來寫腳本。
- 先確保你的電腦本地已經(jīng)安裝了 Nodejs
- 再新建空項目,安裝
Playwright
mkdir bypass-captcha cd bypass-captcha npm init npm i -D playwright
我們采用
Playwright的庫模式,詳細文檔:Playwright
1.在項目根目錄新建一個腳本文件 captcha.js,填入以下內(nèi)容,命令行運行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 站首頁,隨后瀏覽器自動關(guān)閉。
請求 in.php 接口
首先整理下請求http://2captcha.com/in.php接口所需要的參數(shù)有哪些,可以看下參數(shù)列表,我們關(guān)注下必傳參數(shù)
| 參數(shù) | 類型 | 必選 | 描述 |
|---|---|---|---|
| key | String | 是 | 您的 API key |
| method | String | 是 | geetest - 定義您正在發(fā)送的是 Geetest 的驗證碼 |
| gt | String | 是 | 在目標網(wǎng)站上找到的 gt 參數(shù) |
| challenge | String | 是 | 在目標網(wǎng)站上找到的 challenge 參數(shù) |
| api_server | String | 否 | 在目標網(wǎng)站上找到的 api_server 參數(shù) |
| pageurl | String | 是 | 您看到 Geetest 驗證碼時所在網(wǎng)頁的完整 URL |
| header_acao | Integer 默認: 0 | 否 | 0 - 禁用 1 - 啟用。 如果啟用 in.php 將在響應(yīng)中包含 Access-Control-Allow-Origin:* 標頭。 用于 Web 應(yīng)用程序中的跨域 AJAX 請求。 res.php 也支持 |
| pingback | String | 否 | 解決驗證碼時將發(fā)送的 pingback(回調(diào))響應(yīng)的 URL。 URL 應(yīng)該在服務(wù)器上注冊。 更多信息在這里 |
| json | Integer 默認: 0 | 否 | 0 - 服務(wù)器將以純文本形式發(fā)送響應(yīng) 1 - 告訴服務(wù)器以 JSON 格式發(fā)送響應(yīng) |
| soft_id | Integer | 否 | 軟件開發(fā)人員的 ID。 將他們的軟件與 2Captcha 集成的開發(fā)人員將獲得獎勵:軟件用戶支出的 10%。 |
| proxy | String | 否 | 格式: login:password@123.123.123.123:3128 您可以找到更多關(guān)于代理的信息這里。 |
| proxytype | String | 否 | 代理類型: HTTP, HTTPS, SOCKS4, SOCKS5. |
| userAgent | String | 否 | 您的 userAgent 將傳遞給我們的工作人員并用于解決驗證碼。 |
key是需要在 2Captcha 官網(wǎng)注冊賬戶后,后臺面板的賬戶設(shè)置中就有一個API key,當然要起讓 key 生效的話還需要充值一定金額method是一個固定值geetestgt和challenge之前已經(jīng)在網(wǎng)站登錄頁面的接口中看到了。不過這里有個注意點,gt是每個網(wǎng)站只有一個值,B 站這里是固定的ac597a4506fee079629df5d8b66dd4fe,但是challenge是一個動態(tài)值,每次 API 請求都會獲得一個新的challenge值。在頁面上加載驗證碼后,challenge值就會失效。 所以要在網(wǎng)站登錄頁加載的時候監(jiān)聽https://passport.bilibili.com/x/passport-login/captcha這個請求,每次重新識別出新的challenge值。下面會講解如何監(jiān)聽到。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 站首頁
- 點擊頂部的登錄按鈕,會彈出登錄框
- 這時候驗證碼接口已經(jīng)發(fā)出,在這里監(jiān)聽驗證碼接口返回的參數(shù),就能截獲到
gt和challenge的值
const {
chromium
} = require("playwright");
(async () => {
// 選擇Chrome瀏覽器,設(shè)置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秒,防止瀏覽器關(guān)閉太快,來不及看到效果
sleep(5000);
// 關(guān)閉瀏覽器
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
現(xiàn)在可以開始正式的請求 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,表明您的賬戶余額不足,需要充值,我這里充值了最低額度用于演示,大家根據(jù)自己需要適當體驗下。
擴展學習
1.為了提升安全性,我們將 API Key 寫在環(huán)境變量文件中來引用。
在根目錄新建一個環(huán)境變量文件.env,寫入API Key的值
# .env文件 API_KEY="d34y92u74en96yu6530t5p2i2oe3oqy9"
1.然后安裝dotenv庫,用來獲取到環(huán)境變量
npm i dotenv
1.在 js 腳本中使用
require("dotenv").config();這樣通過 process.env.API_KEY 就能取到 .env 中的變量了,通常 .env 文件不上傳到代碼倉庫,保證個人信息的安全性。
如果不想把信息寫到文件中的同時確保安全性,也可以直接在控制臺輸入傳入 Node.js 環(huán)境變量,比如
API_KEY=d34y92u74en96yu6530t5p2i2oe3oqy9 node captcha.js
請求 res.php 接口請求接口之前,我們也整理下所需參數(shù)
| GET 參數(shù) | 類型 | 必選 | 描述 |
|---|---|---|---|
| key | String | 是 | 您的 API key |
| action | String | 是 | get - 得到您的驗證碼的答案 |
| id | Integer | 是 | in.php 返回的驗證碼 ID |
| json | Integer 默認: 1 | 否 | 服務(wù)器將始終以 JSON 格式返回 Geetest 驗證碼的響應(yīng)。 |
key就是API_KEY,上一個接口也用到了action就是固定值getid是剛剛in.php返回的驗證碼ID
???????上一個請求 20 秒之后,再請求 http://2captcha.com/res.php 接口獲取驗證結(jié)果
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 ,每一個參數(shù)都是一個字符串
{
"geetest_challenge": "aeb4653fb336f5dcd63baecb0d51a1f3",
"geetest_validate": "9f36e8f3a928a7d382dad8f6c1b10429",
"geetest_seccode": "9f36e8f3a928a7d382dad8f6c1b10429|jordan"
}其中 challenge 就是前面我們攔截到的參數(shù), validate 是校驗結(jié)果標識, seccode 內(nèi)容和 validate 基本一致,只多了一個單詞。我們需要將 validate 存儲下來備用。
這里有時候會碰到驗證碼無法校驗通過的情況,可以多嘗試幾次,或者聯(lián)系 2Captcha 官網(wǎng)排查問題
到這里,驗證碼校驗結(jié)果的信息已經(jīng)獲取完整,接下來就是拿校驗結(jié)果去登陸了。
登陸
1.先來研究下正常用戶點擊驗證碼校驗成功后的登陸流程

我們發(fā)現(xiàn)了三個接口
https://api.geetest.com/ajax.php:驗證碼接口,用于生成驗證碼和校驗驗證碼是否通過。校驗接口返回數(shù)據(jù)中的 validate 字段就是 2Captcha 服務(wù)獲取到的 geetest_validate。

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

https://passport.bilibili.com/x/passport-login/web/login:登陸接口,入?yún)ㄙ~號、密碼、token、challenge、validate和 seccode等

我們分析這幾個接口,可以得出兩個登陸方案。
- 第一個方案,在
Node.js環(huán)境下請求加密接口和登陸接口,獲取用戶 Cookie 信息,后續(xù)用戶登陸直接攜帶 Cookie 信息即可。這個方案的難點是需要單獨處理密碼加密,對新手不太友好。 - 第二個方案,用
Playwright模擬用戶填寫賬號密碼登陸,隨機點擊驗證碼觸發(fā)登陸,攔截驗證碼接口的返回參數(shù),替換為校驗成功的標識,隨即觸發(fā)登陸接口。
我們采用第二種方案。
不過也遇到一個坑,就是 Node.js 環(huán)境下,驗證碼圖片加載不出來,后面發(fā)現(xiàn)驗證碼接口 https://api.geetest.com/ajax.php 同時負責拉取驗證碼圖片和校驗驗證碼,我們直接攔截拉取驗證碼圖片時的請求,替換校驗結(jié)果就能觸發(fā)登陸了,不需要等圖片驗證碼出來。這個細節(jié)很關(guān)鍵。
到此這篇關(guān)于Nodejs Playwright 2Captcha 驗證碼識別實現(xiàn)自動登陸功能的文章就介紹到這了,更多相關(guān)Nodejs Playwright 2Captcha 驗證碼識別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
node.js中stream流中可讀流和可寫流的實現(xiàn)與使用方法實例分析
這篇文章主要介紹了node.js中stream流中可讀流和可寫流的實現(xiàn)與使用方法,結(jié)合實例形式分析了node.js stream流可讀流和可寫流基本分類、原理、定義、使用方法及相關(guān)注意事項,需要的朋友可以參考下2020-02-02
nodejs進階(6)—連接MySQL數(shù)據(jù)庫示例
本篇文章主要介紹了nodejs進階(6)—連接MySQL數(shù)據(jù)庫示例,詳細的介紹了NodeJS操作MySQL數(shù)據(jù)庫,作為應(yīng)用最為廣泛的開源數(shù)據(jù)庫則成為我們的首選,有興趣的可以了解一下。2017-01-01

