這實際上是一個求助的文章

背景

。。。。最一開始我只是想弄個幫女友自動清購物車的腳本順帶嘗試一下puppeteer,but噩夢開始了javascript

第一階段 / 嘗試使用puppeteer

文檔在這裏
對我這種英語差的人實在是心累,好在api簡潔明瞭。。。雖然有些地方還不完善,不要緊乾的就是盲人摸象的工做。
入門教程網上仍是不少的我隨便貼一個Puppeteer 與 Chrome Headless —— 從入門到爬蟲html

而後是打開淘寶的基礎代碼(太簡單了,沒啥要看的)java

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('進入淘寶登錄頁');
});複製代碼

第二階段 / 非掃碼模式登錄

一開始我覺的掃碼多麻煩,還要把圖片弄到本地啥的,果斷用帳號+密碼+驗證碼來擼,git

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());
        });
    });
}複製代碼

好,這樣實際上是能夠了。。。。可是有幾個問題:github

1. 輸入文字時間間隔

輸入文字不能太快,這個其實調整delay在必定程度上能夠解決chrome

2. 若是你的帳號存在嫌疑,須要滑動驗證

我一開始以爲只要拖過去就能夠了= =,事實證實我太天真,網上找找看到了這傳送門,大概就是說盡可能模擬人的操做。。。。而後我嘗試了canvas

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)
這條路就此斷絕,可是我尚未放棄api

第三階段 / 掃碼模式登錄

嘛。。。掃就掃。。。瀏覽器

本來是想直接保存二維碼到本地而後打開圖片掃碼less

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 = ['█', ' '];複製代碼

這個問題實際上是字符集對於這個特殊字符的定義不一樣,這個就很難辦了。。。。。總不能讓別人都用一種字體

第四階段 / 求助

咳咳。。。你們都來想一想辦法,還有什麼好方法,請在下面留言

相關文章
相關標籤/搜索