之前写了个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);
注意点
--disable-extensions-except
插件路径的写法,在mac中上面写法是正确的。在window中,\
字符需要替换成 /
,这里浪费了好多时间frame
dom树,page.frames()
返回的集合需要找到指定的frame,试了好多方法,最后直接暴力去2了参考连接
新手必须看的
大佬翻译的puppeteer文档
puppeteer之iframe1
puppeteer之iframe2
puppeteer之iframe3
微信群大佬都在等着你
微信扫描二维码加入微信群,交流学习,及时获取代码最新动态。