今天我們來加個 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 函式吧!
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 秒,超過會 Errorpage.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
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 來辨識圖片的文字。
我們學校的驗證碼有兩種模式:四字英文、個位數運算。
我試過四字英文的辨識準確率有點低,而且很難確定辨識到底是不是對的,但數字的可以用是否抓到兩個數字及一個運算子做確認。
所以選擇刷到高辨識成功的數字並可以執行運算再回傳使用。
看來最複雜的驗證碼已經處裡完了,接著明天就是抓資料跟刷加退選資料!
以 9/28 20:00 ~ 9/29 20:00 文章觀看數增加值排名
+458
Day 13 - 密碼破解軟體初體驗
+369
讓程式碼化為 API Doc
+267
Day 1 無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
+225
Day 3 雲端四大平台比較:AWS . GCP . Azure . Alibaba
+221
[Day 27] 資料產品開發實務 - 非機器學習模型
+203
Day 2 AWS 是什麼?又為何企業這麼需要 AWS 人才?
+200
處理 API 層次感之地基篇
+193
Day 4 網路寶石:AWS VPC Region/AZ vs VPC/Subnet 關係介紹
+189
Day 5 網路寶石:AWS VPC 架構 Routes & Security (上)
+171
Day 6 網路寶石:AWS VPC 架構 Routes & Security (下)
密碼破解好像光聽起來就很有趣