├── .gitignore ├── .env ├── package.json ├── README-zh.md ├── README.md └── captcha.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | 4 | .DS_Store -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NAME="lwebapp@163.com" 2 | PWD="openhacking" 3 | API_KEY="d34y92u74en96yu6530t5p2i2oe3oqy9" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bypass-captcha", 3 | "version": "1.0.0", 4 | "description": "Bypass Captcha Automatic Login with Nodejs + Playwright + 2Captcha", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "openHacking ", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "playwright": "^1.20.2" 13 | }, 14 | "dependencies": { 15 | "dotenv": "^16.0.0", 16 | "request": "^2.88.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # Bypass Captcha 2 | 3 | 简体中文 | [English](./README.md) 4 | 5 | ## 介绍 6 | 7 | 使用 Nodejs + Playwright + 2Captcha 自动登陆B站 8 | 9 | 完整分析 [Nodejs Playwright 验证码识别实现自动登陆](https://lwebapp.com/zh/post/bypass-captcha) 10 | 11 | ## 使用 12 | 13 | 1. 拉取项目,安装依赖 14 | ```sh 15 | git clone https://github.com/openHacking/bypass-captcha.git 16 | cd bypass-captcha 17 | npm i 18 | ``` 19 | 20 | 2. 修改`.env`文件中的登陆信息和 `API Key` 21 | 22 | 3. 启动 23 | ```sh 24 | node captcha.js 25 | ``` 26 | 27 | ## 参考 28 | 29 | - [Playwright](https://playwright.dev/) 30 | - [2Captcha](https://2captcha.com?from=13803059) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bypass Captcha 2 | 3 | English | [简体中文](./README-zh.md) 4 | 5 | ## Introduction 6 | 7 | Use Nodejs + Playwright + 2Captcha, bypass captcha and automatically log in to bilibili.com 8 | 9 | Complete analysis [How to Bypass Captcha Automatic Login with Nodejs + Playwright + 2Captcha](https://lwebapp.com/en/post/bypass-captcha) 10 | 11 | ## Usage 12 | 13 | 1. Pull the project and install the dependencies 14 | 15 | ```sh 16 | git clone https://github.com/openHacking/bypass-captcha.git 17 | cd bypass-captcha 18 | npm i 19 | ``` 20 | 21 | 2. Modify the login information and `API Key` in the `.env` file 22 | 23 | 3. Start 24 | 25 | ```sh 26 | node captcha.js 27 | ``` 28 | 29 | ## Reference 30 | 31 | - [Playwright](https://playwright.dev/) 32 | - [2Captcha](https://2captcha.com?from=13803059) 33 | -------------------------------------------------------------------------------- /captcha.js: -------------------------------------------------------------------------------- 1 | const { chromium } = require("playwright"); 2 | const request = require("request"); 3 | 4 | require("dotenv").config(); 5 | 6 | const NAME = process.env.NAME; 7 | const PWD = process.env.PWD; 8 | const API_KEY = process.env.API_KEY; 9 | const METHOD = "geetest"; 10 | const PAGE_URL = "https://www.bilibili.com/"; 11 | let validate = ""; 12 | 13 | const RES_RETRY_ERROR = [ 14 | "CAPCHA_NOT_READY", 15 | "ERROR_CAPTCHA_UNSOLVABLE", 16 | "ERROR_BAD_DUPLICATES", 17 | ]; 18 | 19 | (async () => { 20 | // 选择Chrome浏览器,设置headless: false 能看到浏览器界面,devtools: true 打开控制台 21 | const browser = await chromium.launch({ 22 | headless: false, 23 | // devtools: true, 24 | }); 25 | 26 | const page = await browser.newPage(); 27 | 28 | // 提前监听请求,修改校验参数 29 | await page.route("https://api.geetest.com/ajax.php?**", async (route) => { 30 | // Fetch original response. 31 | const response = await page.request.fetch(route.request()); 32 | 33 | let body = await response.text(); 34 | 35 | console.log("修改验证码校验接口 ajax.php,使用 validate", validate); 36 | // 修改为校验成功 37 | let callback = body.split("(")[0]; 38 | body = 39 | callback + 40 | "(" + 41 | JSON.stringify({ 42 | status: "success", 43 | data: { 44 | result: "success", 45 | validate: validate, 46 | score: "99", 47 | msg: [], 48 | }, 49 | }) + 50 | ")"; 51 | route.fulfill({ 52 | // Pass all fields from the response. 53 | response, 54 | // Override response body. 55 | body, 56 | headers: response.headers(), 57 | }); 58 | }); 59 | 60 | console.log("打开B站"); 61 | 62 | // 打开B站 63 | await page.goto(PAGE_URL); 64 | 65 | console.log("点击顶部按钮,请求验证码接口"); 66 | 67 | const [response] = await Promise.all([ 68 | // 请求验证码接口 69 | page.waitForResponse( 70 | (response) => 71 | response.url().includes("/x/passport-login/captcha") && 72 | response.status() === 200 73 | ), 74 | // 点击顶部的登录按钮 75 | page.click(".header-login-entry"), 76 | ]); 77 | 78 | // 获取到接口返回信息 79 | const responseJson = await response.body(); 80 | 81 | // 解析出 gt 和 challenge 82 | const json = JSON.parse(responseJson); 83 | const gt = json.data.geetest.gt; 84 | const challenge = json.data.geetest.challenge; 85 | 86 | console.log("得到 gt", gt, "challenge", challenge); 87 | 88 | // 请求 in.php 接口 89 | 90 | const inData = { 91 | key: API_KEY, 92 | method: METHOD, 93 | gt: gt, 94 | challenge: challenge, 95 | pageurl: PAGE_URL, 96 | json: 1, 97 | }; 98 | 99 | console.log("in.php 请求"); 100 | 101 | postInPHP(inData) 102 | .then((id) => { 103 | console.log("in.php 成功 id", id); 104 | 105 | console.log("等待20秒"); 106 | sleep(20000); 107 | 108 | console.log("res.php 请求"); 109 | getResPHP(id) 110 | .then((data) => { 111 | console.log("res.php 成功 ", data); 112 | 113 | // 存储校验结果 114 | validate = data && data.geetest_validate; 115 | 116 | login(page); 117 | }) 118 | .catch((e) => { 119 | console.error("res.php 报错", e); 120 | }); 121 | }) 122 | .catch((e) => { 123 | console.error("in.php 返回", e); 124 | }); 125 | })(); 126 | 127 | function postInPHP(inData) { 128 | return new Promise((resolve, reject) => { 129 | request.post( 130 | "http://2captcha.com/in.php", 131 | { json: inData }, 132 | function (error, response, body) { 133 | if (!error && response.statusCode == 200) { 134 | // 如果显示 ERROR_ZERO_BALANCE,表明您的账户余额不足,需要充值,支持 135 | if (body.status == 1) { 136 | resolve(body.request); 137 | } else { 138 | reject(body); 139 | } 140 | } else { 141 | reject(error); 142 | } 143 | } 144 | ); 145 | }); 146 | } 147 | 148 | function getResPHP(ID) { 149 | return new Promise((resolve, reject) => { 150 | let count = 0; 151 | 152 | res(); 153 | 154 | function res() { 155 | request.get( 156 | `http://2captcha.com/res.php?key=${API_KEY}&action=get&id=${ID}&json=1`, 157 | function (error, response, body) { 158 | if (!error && response.statusCode == 200) { 159 | const data = JSON.parse(body); 160 | 161 | // res.php支持重新发送请求的情况下,重新尝试,超过5次没有成功也退出 162 | if ( 163 | data.status == 0 && 164 | RES_RETRY_ERROR.includes(data.request) && 165 | count < 5 166 | ) { 167 | count++; 168 | 169 | console.log("res.php 返回", data.request, "重新请求"); 170 | sleep(5000); 171 | res(); 172 | } else if (data.status == 1) { 173 | resolve(data.request); 174 | } else { 175 | reject(data); 176 | } 177 | } else { 178 | reject(error); 179 | } 180 | } 181 | ); 182 | } 183 | }); 184 | } 185 | 186 | async function login(page) { 187 | console.log("填入用户名、密码,开始登陆"); 188 | // 在登录弹框内填入账号和密码,我们采用Nodejs的命令行设置process环境变量,来输入自己的账号密码,防止将账号密码写入代码中 189 | await page.locator(".bili-mini-account input").fill(NAME); 190 | 191 | sleep(1000); 192 | 193 | await page.locator(".bili-mini-password input").fill(PWD); 194 | 195 | console.log("点击弹框内的登录按钮"); 196 | 197 | sleep(1000); 198 | 199 | // 点击弹框内的登录按钮 200 | const handle = await page.$(".login-btn"); 201 | await handle.hover(); 202 | await handle.click(); 203 | } 204 | 205 | /** 206 | * 模拟sleep功能,延迟一定时间,单位毫秒 207 | * Delay for a number of milliseconds 208 | */ 209 | function sleep(delay) { 210 | var start = new Date().getTime(); 211 | while (new Date().getTime() < start + delay); 212 | } 213 | --------------------------------------------------------------------------------