之前寫了個chrome 擴充來完成公司内部的一個需求。在一個網站上實作了自動化程式,包括登入,後續操作,保持狀态,被踢出後再次登入等等。
但是這個網站突然前幾天更新了登入方式,在登入頁面嵌入了iframe,使用内嵌的iframe登入。我還是用chrome擴充試了一下,但是chrome擴充無法操作iframe。
隻能轉換思路,最終群友提供了一個線索,找到了一個google官方出的node包
puppeteer
,順利的完成了自動登入的流程。隻能說這個包真強大,群友玩的真多。
是以以下内容和代碼都為了解決一個問題:
使用 puppeteer 自動登入内嵌 iframe 頁面登入的的網頁,并監控到登入狀态失效後,自動再次登入
本文預設您會使用
node
和
npm
, 以下腳本基于node14.15.0,在node 12版本上運作通過
所需要的插件包
"devDependencies": {
"carlo": "^0.9.46",
"puppeteer-core": "^5.4.0"
}
puppeteer
和
puppeteer-core
的差別
puppeteer
的核心是
puppeteer-core
,
puppeteer
會下載下傳
Chromium
,而
puppeteer-core
不會自動下載下傳
Chromium
。
puppeteer
運作時預設使用
puppeteer-core
來驅動
Chromium
,
puppeteer
還能配置
PUPPETEER_*
。我這裡使用了
puppeteer-core
,調用本地
google chrome
。
準備工作
const puppeteer = require('puppeteer-core');
//find_chrome子產品來源于GoogleChromeLabs的Carlo,可以檢視本機安裝Chrome目錄
const findChrome = require('./node_modules/carlo/lib/find_chrome');
const width = 1366; //浏覽器寬度
const height = 768; //浏覽器高度
let browser = null
, page = null
, init = false // 初次運作腳本
, isOk = false; // 觸發登入事件開關
以上導入了必須的子產品,提前聲明了需要的資料
1 傳入配置 建立浏覽器對象
const newBrowser = async () => {
init = true; // 已經建立了浏覽器對象
let findChromePath = await findChrome({});
let executablePath = findChromePath.executablePath; //得到本機chrome路徑
browser = await puppeteer.launch({
executablePath, // 本地chrome路徑
headless: false, // 啟用頁面GUI方式
devtools: false, // F12打開控制台
args: [
`--disable-extensions-except=/Users/mac/project/debug/rechargenew`, // 不屏蔽這個擴充
`--window-size=${width},${height}`, // 視窗大小
`–disable-gpu` // 禁用 GPU加速
],
defaultViewport: { width: width, height: height } // 頁面大小
});
page = await browser.newPage(); // 建立浏覽器
newhtml()
}
此時到這裡桌面就會打開一個chrome浏覽器
2 打開指定的頁面
// 建立打開google頁面
const newhtml = async () => {
await page.goto('http://www.xxxx.com/', {
waitUntil: 'networkidle2'
});
startLogin()
}
運作到這裡會打開指定頁面
3 開啟自動登入
// 開始登入
const startLogin = async (callback) => {
const startLogin = await page.$(".submit-btn");
if (startLogin) {
page.click(".submit-btn")
}
// 檢測到 iframe 請求傳回回來了 再等5秒鐘開始自動填寫賬号密碼登入
page.on('response', async req => {
// 判斷目前這個請求是不是請求 iframe 登入頁面
if (req.url().indexOf('xxxx.xxxx.com/xxxx') >= 0 && !isOk) {
isOk = true // 阻止重複運作
setTimeout(async () => {
await page.waitFor('[id*="xxxxx"]');//等待我的iframe出現
const frame = (await page.frames())[2]
frame.click("#tab-password")
await frame.waitFor(3000);
await frame.waitFor('.ruleForm-pwd .form-item1 input');//等待使用者名框出現
await frame.type('.ruleForm-pwd .form-item1 input', 'xxxx');//輸入使用者名
await frame.waitFor('.ruleForm-pwd .passwordInput input');//等待密碼輸入框出現
await frame.type('.ruleForm-pwd .passwordInput input', 'xxxx');//輸入密碼
// 點選登入
setTimeout(() => {
frame.click(".loginBtnWrap .loginBtn")
setTimeout(() => {
page.click(".banner-info .submit-btn")
}, 2000);
}, 1000);
}, 5000);
}
});
}
在這裡使用
page.on('response')
監聽到特定的請求接口傳回後,開始填入賬号和密碼準備登入
有些地方需要用到延時器等待dom或者js操作完成,才能進行下一步,這樣較為保險。
檢測登入狀态是否失效
// 檢查是否被踢出登入 被踢出登入後重新登入
setInterval(() => {
console.log(page.url());
if (page.url().indexOf("xxx.xxx.com/index.html?") >= 0 && init) {
isOk = false
newhtml()
}
}, 20000);
這裡開啟了一個定時器,判斷目前宿首頁面的url是否是登入頁面的url,如果是登入頁面的url,就判定目前登入狀态已經失效了,然後再次重新開機登入流程。
完整代碼:
const puppeteer = require('puppeteer-core');
//find_chrome子產品來源于GoogleChromeLabs的Carlo,可以檢視本機安裝Chrome目錄
const findChrome = require('./node_modules/carlo/lib/find_chrome');
const width = 1366;
const height = 768;
let browser = null
, page = null
, init = false // 初次運作
, isOk = false; // 觸發登入事件開關
const newBrowser = async () => {
init = true;
let findChromePath = await findChrome({});
let executablePath = findChromePath.executablePath;
browser = await puppeteer.launch({
executablePath,
headless: false,
devtools: false, // F12打開控制台
args: [
`--disable-extensions-except=/Users/mac/project/debug/rechargenew`, // 不屏蔽這個插件
`--window-size=${width},${height}`, // 視窗大小
`–disable-gpu`
],
defaultViewport: { width: width, height: height } // 頁面大小
});
page = await browser.newPage();
newhtml()
}
// 建立打開google頁面
const newhtml = async () => {
await page.goto('http://www.xxxx.com/', {
waitUntil: 'networkidle2'
});
startLogin()
}
// 開始登入
const startLogin = async (callback) => {
const startLogin = await page.$(".submit-btn");
if (startLogin) {
page.click(".submit-btn")
}
// 檢測到 iframe 請求傳回回來了 再等5秒鐘開始自動填寫賬号密碼登入
page.on('response', async req => {
if (req.url().indexOf('xxxxx.xxxxxx.com/xxxx') >= 0 && !isOk) {
isOk = true // 阻止重複運作
setTimeout(async () => {
await page.waitFor('[id*="xxxxx"]');//等待我的iframe出現
const frame = (await page.frames())[2]
frame.click("#tab-password")
await frame.waitFor(3000);
await frame.waitFor('.ruleForm-pwd .form-item1 input');//等待使用者名框出現
await frame.type('.ruleForm-pwd .form-item1 input', 'xxxxxxx');//輸入使用者名
await frame.waitFor('.ruleForm-pwd .passwordInput input');//等待密碼輸入框出現
await frame.type('.ruleForm-pwd .passwordInput input', 'xxxxx');//輸入密碼
// 點選登入
setTimeout(() => {
frame.click(".loginBtnWrap .loginBtn")
setTimeout(() => {
page.click(".banner-info .submit-btn")
}, 2000);
}, 1000);
}, 5000);
}
});
}
newBrowser()
// 檢查是否被踢出登入 踢出登入後重新登入
setInterval(() => {
console.log(page.url());
if (page.url().indexOf("xxxxxxx.xxxxxxx.com/index.html?") >= 0 && init) {
isOk = false
newhtml()
}
}, 20000);
注意點
注意點
- 合理運用定時器和延時器,防止dom還未生成時,使用了相關代碼
-
插件路徑的寫法,在mac中上面寫法是正确的。在window中,--disable-extensions-except
字元需要替換成\
,這裡浪費了好多時間🐶/
- 擷取
dom樹,frame
傳回的集合需要找到指定的frame,試了好多方法,最後直接暴力去2了page.frames()
參考連接配接
新手必須看的
大佬翻譯的puppeteer文檔
puppeteer之iframe1
puppeteer之iframe2
puppeteer之iframe3
微信群大佬都在等着你
微信掃描二維碼加入微信群,交流學習,及時擷取代碼最新動态。