iT邦幫忙

2021 iThome 鐵人賽

DAY 15
1
Modern Web

JavaScript Easy Go!系列 第 15

#15 Automation (3)

今天我們來加個 retry 函式,因為我覺得這個程式可能會出莫名其妙的問題。
然後說說如何處裡圖片驗證碼。

Retry

retry 函式的用處是當我們一個程式執行時發生錯誤跳出時可以重新執行該失敗的程式,直至指定最大嘗試次數。
因為可能會因為網路問題或莫名其妙的原因導致我們要抓的元素消失,而 puppeteer 對應的處裡方法就是拋出一個錯誤給你,為了避免小機率發生的怪怪問題讓整個程式崩潰,我們需要有 retry,搞不好第二次執行就好了。

async function retry(max, func) {
    // 當然,func 要是一個 function 我們才能執行它
    if (typeof func !== "function") throw new Error("Second Argument Must Be A Function.");
    let i = 0;
    while (i++ < max) {
        try {
            // 加上 await 是因為我們不確定 func 是否為 async,如果是的話就會直接回傳 Promise
            return await func();
        } catch (e) {
            console.error(`${func.name} Failed ${i} `, e.message);
        }
    }
    // 如果真的直到指定最大嘗試次數每次都失敗了,還是得給個錯誤
    throw new Error(`${func.name} Failed After ${max} Attempts.`);
}

當我們要用 retry 時:

await retry(3, login);
// 或是加上參數
await retry(3, async (something) => f(something))

retry 算是還蠻通用的,不限於這個自動化的程式,在很多地方都可以用。

login

好了,來寫 login 函式吧!

async function login() {
    // 前往登入頁面
    await page.goto("https://cos1s.ntnu.edu.tw/AasEnrollStudent/LoginCheckCtrl?language=TW");

    // 等待需要操作的元素出現
    await Promise.all([
        page.waitForSelector("#userid-inputEl"),
        page.waitForSelector("#password-inputEl"),
        page.waitForSelector("#validateCode-inputEl"),
        page.waitForSelector("#button-1016"),
    ]);

    // 填入帳號密碼
    await page.type("#userid-inputEl", config.username);
    await page.type("#password-inputEl", config.password);

    // 處裡驗證碼
    let code = await page.evaluate(injection.getValidateCode);
    console.log("Validation: ", code);
    await page.type("#validateCode-inputEl", code.toString());

    // 按下登入按鈕
    await page.click("#button-1016");

    // 等待頁面跳轉
    await page.waitForNavigation({ timeout: 5000 });

    // 如果跳轉至系統內代表成功
    if (page.url().includes("IndexCtrl")) console.log("Login Successfully");
    else throw new Error("Login Failed");
}
  • page.goto: 操作某分頁至指定網址
  • page.waitForSelector: 等待某元素出現,預設是等 30 秒,超過會 Error
  • page.type: 輸入文字
  • page.evaluate: 這個是在網頁上執行一段程式,在這是 injection.getValidateCode (等等會說)
  • page.click: 按按鈕
  • page.waitForNavigation: 等待跳轉,預設也是等 30 秒,超過會 Error,這裡設成 10 秒
  • page.url: 就 URL,值得注意的是它直接回傳字串,而非 Promise

等等,怎摩會突然冒出 injection.getValidateCode
為了避免程式碼太亂,我們把要注入 (inject) 的程式碼移到另一個新檔案 injection.js,再用 require 的方法引入到 index.js 中使用。

injection.js

目前只有一個 ,其他的之後會增加。

// injection.js
async function getValidateCode() {
    // 載入 tesseract.js
    await new Promise((resolve) => {
        let script = document.createElement("script");
        script.src = "https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js";
        script.onload = resolve;
        document.body.appendChild(script);
    });

    let finalCode = "";
    // 抓數字及運算子,如果沒有找到就一直刷
    while (!finalCode.match(/[0-9]/g) || finalCode.match(/[0-9]/g).length !== 2 || !getOp(finalCode)) finalCode = await getCode();
    const nums = finalCode.match(/[0-9]/g).map((n) => parseInt(n)),
        op = getOp(finalCode);
    let ans = 0;
    // 做計算
    if (op === "+") ans = nums[0] + nums[1];
    else if (op === "-") ans = nums[0] - nums[1];
    else if (op === "*") ans = nums[0] * nums[1];
    console.log(finalCode, nums, op, ans);
    
    // 回傳計算結果至我們的程式
    return ans;

    // 用 tesseract.js 從驗證碼圖片抓文字出來
    async function getCode() {
        console.log("getCode!");
        const worker = Tesseract.createWorker({ logger: (m) => console.log(m) });
        await worker.load();
        await worker.loadLanguage("eng");
        await worker.initialize("eng");
        const {
            data: { text },
        } = await worker.recognize(document.querySelector("#imageBoxCmp"));
        await worker.terminate();
        return text;
    }
    // 從結果中判定運算子
    function getOp(code) {
        let op = null;
        if (code.match(/[+]/g)) op = "+";
        else if (code.match(/[-_]/g)) op = "-";
        else if (code.match(/[*]/g)) op = "*";
        return op;
    }
}

exports.getValidateCode = getValidateCode;
// index.js
const injection = require("./injection");

我們用 tesseract.js 來辨識圖片的文字。
我們學校的驗證碼有兩種模式:四字英文、個位數運算。
我試過四字英文的辨識準確率有點低,而且很難確定辨識到底是不是對的,但數字的可以用是否抓到兩個數字及一個運算子做確認。
所以選擇刷到高辨識成功的數字並可以執行運算再回傳使用。

明天

看來最複雜的驗證碼已經處裡完了,接著明天就是抓資料跟刷加退選資料!


每日鐵人賽熱門 Top 10 (0928)

以 9/28 20:00 ~ 9/29 20:00 文章觀看數增加值排名

  1. +458 Day 13 - 密碼破解軟體初體驗
    • 作者: 大谷尼奇
    • 系列:讓 Hacking for Dummies 一書陪我 30 天
  2. +369 讓程式碼化為 API Doc
    • 作者: Chris
    • 系列:Vue.js 進階心法
  3. +267 Day 1 無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  4. +225 Day 3 雲端四大平台比較:AWS . GCP . Azure . Alibaba
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  5. +221 [Day 27] 資料產品開發實務 - 非機器學習模型
    • 作者: bryanyang0528
    • 系列:資料產品開發與專案管理
  6. +203 Day 2 AWS 是什麼?又為何企業這麼需要 AWS 人才?
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  7. +200 處理 API 層次感之地基篇
    • 作者: Chris
    • 系列:Vue.js 進階心法
  8. +193 Day 4 網路寶石:AWS VPC Region/AZ vs VPC/Subnet 關係介紹
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  9. +189 Day 5 網路寶石:AWS VPC 架構 Routes & Security (上)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  10. +171 Day 6 網路寶石:AWS VPC 架構 Routes & Security (下)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題

密碼破解好像光聽起來就很有趣


上一篇
#14 Automation (2)
下一篇
#16 Automation (4)
系列文
JavaScript Easy Go!31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言