#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/[email protected]/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 云端必学主题

密码破解好像光听起来就很有趣


<<:  第 13 天 坚持刷题持续进步( leetcode 016 )

>>:  LeetCode解题 Day29

component

今天要介绍的是component 在一个网页中我们常会看到header、Footer、侧边拦等等,在...

Day#07 新增(2)

前言 接续着前一天没做完的新增功能,今天继续接着做~边做也一边介绍用到的方法与程序码。 Storyb...

Day29 Plugin 从零开始到上架 - 上架

官方文件 完成我们Plugin 的功能後,我们要建立一些文件,之後upload 至pub.dev 後...

Proxmox VE 网路进阶设定 (Bridge、LACP、VLAN)

在规模较大的企业网路中,为了避免单点故障会采用 LACP 的方式将多条线路聚合在一起使用,除了增加...

Day9 盒模型

盒模型概念 所有的HTML元素就像是箱子一样(有宽度高度),并且具备以下六个属性: 宽度width...