背景
。。。。最一开始我只是想弄个帮女朋友自动清购物车的脚本顺带尝试一下puppeteer,but噩梦开始了
第一阶段 / 尝试使用puppeteer
文档在
对我这种英语差的人实在是心累,好在api简洁明了。。。虽然有些地方还不完善,没关系干的就是盲人摸象的工作。入门教程网上还是很多的我随便贴一个然后是打开淘宝的基础代码(太简单了,没啥要看的)
puppeteer.launch({ headless: false}).then(async browser => { const page = await browser.newPage(); // 设置Viewport await page.setViewport({ width: 1366, height: 768 }); await page.goto('https://login.taobao.com/member/login.jhtml'); console.log('进入淘宝登陆页');});复制代码
第二阶段 / 非扫码模式登陆
一开始我觉的扫码多麻烦,还要把图片弄到本地啥的,果断用账号+密码+验证码来撸,
puppeteer.launch({ headless: false}).then(async browser => { ...这里代码就不重复了 await page.waitForSelector('#J_Quick2Static'); await page.click('#J_Quick2Static'); console.log('切换至普通模式登陆'); // 判断一下登陆账号密码是否存在,不存在就输入 loginData = Object.keys(loginData).length ? loginData : await inputLoginData(); console.log('成功获取用户账号密码,开始输入'); await sleep(1000); const usernameInput = await page.$('input[name=TPL_username]'); await usernameInput.click(); await usernameInput.type(loginData.username, { delay: 50 }); 密码也是这样 console.log('输入完毕'); const loginButton = await page.$('#J_SubmitStatic'); await loginButton.click(); console.log('登陆成功');});复制代码
// 用户输入账号密码的函数,可以不用看const inputLoginData = async () => { const result = {}; await readSyncByRl('输入淘宝账号: ').then((msg) => { result.username = msg; }); await readSyncByRl('输入淘宝密码: ').then((msg) => { result.password = msg; }); return result;}function readSyncByRl(tips) { tips = tips || '> '; return new Promise((resolve) => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question(tips, (answer) => { rl.close(); resolve(answer.trim()); }); });}复制代码
好,这样其实是可以了。。。。但是有几个问题:
1. 输入文字时间间隔
输入文字不能太快,这个其实调整delay
在一定程度上可以解决
2. 如果你的账号存在嫌疑,需要滑动验证
我一开始觉得只要拖过去就可以了= =,事实证明我太天真,网上找找看到了这,大概就是说尽量模拟人的操作。。。。然后我尝试了
await page.$('#nc_1_n1z').then(async (element) => { const point = await element.boundingBox(); console.log('进行滑动验证'); await moveSlide(point, page);}).catch(() => { console.log('无滑动验证码');});复制代码
// 滑动验证async function moveSlide(point, page) { await page.waitForSelector('#nc_1_n1z', { visible: true, }); await sleep(1000); // 果然高中物理都还给老师了,下面模拟一个16加速度,时长为2的滑动 const mouse = page.mouse; const x = point.x + Math.round(Math.random() * point.width / 4) + point.width / 3; const y = point.y + Math.round(Math.random() * point.height / 4) + point.height / 3; await mouse.move(x, y); await mouse.down(); await sleep(200); for (let i = 2; i > 0; i--) { await mouse.move(x + 16 * i ** 2, y + 0.2 * i ** 2, { steps: 2, }); } for (let i = 8; i > 0; i--) { await mouse.move(x + 64 + 32 * i, y + 0.2 * i ** 2, { steps: 2, }); } await mouse.up(); // 判断验证s是否通过 let ncResult = await Promise.race([ page.waitForSelector('#nocaptcha .btn_ok', { visible: true, }).then(() => { return true; }), page.waitForSelector('#nocaptcha', { visible: true, }).then(() => { return false; }), ]); // 失败重新滑动 if (!ncResult) { await page.waitForSelector('#nocaptcha a', { visible: true, }); await sleep(1000); const nocaptcha = await page.$('#nocaptcha a'); await nocaptcha.click(); await sleep(1000); await moveSlide(point, page); }}复制代码
本来这样有3成的概率能过。。。。但是一段时间后,我发现一直都是失败,我就人工试了试,发现我的账号只要出现滑动严重人工都不能验证通过,不然就是直接可以登陆进去(T_T)
这条路就此断绝,但是我还没有放弃第三阶段 / 扫码模式登陆
嘛。。。扫就扫。。。
原本是想直接保存二维码到本地然后打开图片扫码
await page.waitForSelector('#J_QRCodeImg');await page.$('#J_QRCodeImg').then(async (element) => { const point = await element.boundingBox(); await page.screenshot({ path: "code.png", clip: point, });});复制代码
想想太不好了,显得太傻。。。突然想到了把图片转成ascii字符图,还挺好的,就只有黑白都不用做灰度处理。
let img = new Image();var result = '';img.src = document.getElementById('J_QRCodeImg').childNodes[0].src + '?t=123';img.crossOrigin = "Anonymous";img.onload = async () => { let canvas = document.createElement('canvas'); let canvasContext = canvas.getContext("2d"); canvasContext.drawImage(img, 0, 0); let data = []; for (let h = 0; h < img.height; h += 2) { for (let w = 0; w < img.width; w += 2) { let imgData = canvasContext.getImageData(w, h, 2, 2); let imgDataArray = imgData.data; data.push(imgDataArray.reduce((sum, value) => { return sum + value; }) / (2*2*4)); } } let arr = ['██', ' ']; data.forEach((item, index) => { result += arr[Math.floor(item / 157)]; if ((index + 1) % 70 == 0) { result += '\n'; } })}复制代码
怎么执行呢?
executionContext这个就是exec在浏览器内部的对象
const qrcode = await executionContext.evaluate(async () => { 上文内容 // 让程序停2S防止返回空字符串 await sleep(2000); return Promise.resolve(result); async function sleep(delay) { return new Promise((resolve, reject) => { setTimeout(() => { try { resolve(1) } catch (e) { reject(0) } }, delay); }); };});复制代码
其实乍一看效果还是不错的,就是有点大,在chrome上这个特殊字符宽高比是1:2
但是在cmd上就不是这回事了
在cmd中宽高是1:1所以
// 不适用// let arr = ['██', ' '];let arr = ['█', ' '];复制代码
这个问题其实是字符集对于这个特殊字符的定义不同,这个就很难办了。。。。。总不能让别人都用一种字体
第四阶段 / 求助
咳咳。。。大家都来想想办法,还有什么好方法,请在下面留言