github地址 Puppeteer, 中文文档地址 Puppeteer
介绍
Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.
Puppeteer是由Chrome团队开发的Node库, 可以用来控制Chrome或者Chromium.
那么可以用来做什么呢?
大部分你能手动在Chrome完成的事情都能用Puppeteer实现! 以下是一些入门指南:
- 生成页面截图和PDF.
- 抓取SPA(单页应用程序)并生成预渲染的内容(即“ SSR”(服务器端渲染)).
- 自动表单提交, UI 测试, 键盘输入, 等等.
- 创建最新的, 自动化测试环境. 使用最新的JavaScript和浏览器功能,直接在最新版本的Chrome中运行测试。
- 捕获你的站点的 timeline trace 帮助诊断性能问题.
- 测试Chrome扩展程序.
自动健康打卡涉及的操作就是提交表单, 点击按钮, 发送请求等.
用法
mkdir puppeteer-test
cd puppeteer-test
npm init
npm i puppeteer-core
# or "yarn add puppeteer-core"这里选择安装puppeteer-core. puppeteer-core版本不会安装Chromium, 因为Chromium在下载可能会出现问题. 之后直接使用Chrome就可以. 当然也可以手动下载最新版的Chromium. 两者区别可以看 puppeteer vs puppeteer-core.
创建index.js
const main = async () => {
const browser = await puppeteer.launch({
//启动
executablePath:
"C:\\Program Files (x86)\\Google\\Chrome Dev\\Application\\chrome.exe", //Chrome路径, 可以右键Chrome->属性->目标
headless: false // 是否以无头模式运行, 默认ture. 无头就是不打开Chrome图形界面, 更快.
});
const page = await browser.newPage(); // 打开一个页面, page就是后序将要操作的
page.setDefaultNavigationTimeout(120000); // 设置页面的打开超时时间, 因为我要打卡的是学校的垃圾服务器, 超时时间设置了2分钟
try {
await page.goto("https://baidu.com", { waitUntil: "domcontentloaded" }); //页面跳转, 第二个参数为可选options, 这里表示等待页面结构加载完成, 无需等待img等资源
console.log("登录页加载成功!"); //控制台输出一下进度
// 登陆
await page.evaluate(() => {
//在当前页面执行js代码, 也就是在浏览器环境执行代码, 可以使用所有的浏览器API
const usernameInput = document.querySelector("input[name=username]"); //用户名input
const passwordInput = document.querySelector("input[name=password]"); //密码input
const loginInput = document.querySelector("input[name=login_submit]"); //登陆按钮
usernameInput.value = "zhangsan"; //输入用户名密码
passwordInput.value = "123abc";
loginInput.click(); //点击登陆
});
await page.waitForNavigation(); //因为要跳转页面, 所以这里等待页面导航
// 查找打卡选项入口在哪, 我的打卡入口登陆后->推荐列表->本科生健康状况申报
console.log("登陆成功!");
const url = await page.evaluate(() => {
const recommendList = [...document.querySelectorAll(".recommendList li")]; // 获取推荐列表所有的项目
for (let i = 0; i < recommendList.length; i++) {
const item = recommendList[i];
if (
item.querySelector(".app_name").innerText === "本科生健康状况申报"
) {
// 文字说明在app_name下面
return item.getAttribute("appurl"); //dom的appurl属性是我下一步要跳转的地址, 将这个地址返回, 这样node环境就能拿到了
}
}
});
await page.goto(url, { waitUntil: "networkidle0" }); //跳转, 这里表示等待所有网络请求完结, 默认请求不在出现后等待500ms
console.log("表格加载成功!"); //等待结束后我这里会出现填写健康信息的表格
// 填报信息, 我这里填写第一次后, 后序打卡默认都填好了, 只需点击承诺, 点击确定
await page.evaluate(() => {
// 点击承诺
[...document.querySelectorAll(".infoplus_checkLabel")].pop().click(); //所有checkbox的最后一个是我要的承诺checkbox
// 点击确认
document.querySelector(".command_button_content").click(); //点击确定
});
// 这里我点击确定后会有一个请求, 等请求结束后在进行下一步
const res = await page.waitForResponse(
response =>
response.url() ===
"https://baidu.com/infoplus/interface/listNextStepsUsers" &&
response.status() === 200
);
// res.json().then(data => console.log(data)) // 可以把请求内容打出来看看
// 最后再点一下 好
await page.evaluate(() => {
document.querySelector(".dialog_footer button").click();
});
await browser.close(); //关闭浏览器结束
} catch (err) {
console.log("签到过程出错了!");
await browser.close();
throw err;
}
};
说明
- 非
headless模式下运行可以直观地看到浏览器操作过程, 有助于调试 - try catch不必须, 不过学校服务器垃圾, 失败率太高, 需要错误信息做其他操作
- 具体怎么登陆, 怎么填写信息, 具体情况具体分析.
- 每次浏览器跳转都需要等待一下才能进行下一步操作, 不知道等多久等到什么时候可以直接
page.waitFor(60000), 单位ms - 提交表单不一定非得点击按钮, 可以直接向服务器发送请求, 可以在浏览器环境执行
fetch
更多功能
现在, 可以在index写下main(), 然后执行node index, 完成一次打卡. 但是, 这样和手动打卡也没多大区别. 所以, 我们需要设置一个定时任务.
npm的包cron是非常好的选择.
同时打卡是否成功需要有一个提示, 用到node发送邮件包nodemailer
npm i cron nodemailer
index.js中
module.exports = mainmail.js
const nodemailer = require("nodemailer");
const sendMail = msg => {
nodemailer.createTestAccount((err, account) => {
let transporter = nodemailer.createTransport({
service: "qq", // 使用了内置传输发送邮件 查看支持列表:https://nodemailer.com/smtp/well-known/
port: 465, // SMTP 端口
secureConnection: true, // 使用了 SSL
auth: {
user: "10001.qq.com",
// 这里密码不是qq密码,是你设置的smtp授权码
pass: "uiogamsiwefadfs"
}
});
let mailOptions = {
from: "Tony <10001@qq.com>", // 发送者
to: "10002.qq.com", // 接收者
subject: "打卡提醒", // 主题
html: msg // html body
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log("Message sent: %s", info.messageId);
});
});
};
module.exports = sendMail;
cron.js
const CronJob = require("cron").CronJob; // 引入
const tick = require("./main"); // 定时每次执行的任务
const sendMail = require("./mail");
const createJob = (success, fail) => {
// success成功回调, fail失败回调
let succeed = false; // 今天是否打卡成功
let today = new Date().getDate(); // 今天的日期
const job = new CronJob(
"0 20 0-11 * * *",
async onComplete => {
// 主任务, 参数分别代表任务时间, 任务, 任务完成回调
const _today = new Date().getDate(); //任务执行时的日期
if (today !== _today) {
// 如果任务执行的日期和之前的日期不一样说明已经是第二天了
succeed = false; // 第二天, 还没打卡, succeed = false
today = _today;
}
if (!succeed) {
try {
await tick();
} catch (err) {
fail && fail();
onComplete("失败!");
succeed = false;
return;
}
succeed = true;
success && success();
onComplete("成功!");
}
},
s => {
// onComplete
console.log(s, new Date().toLocaleString());
console.log("----------");
}
);
job.start(); // 任务开始
};
const success = () => {
sendMail(`打卡成功 ${new Date().toLocaleString()}`);
};
const fail = () => {
sendMail(`打卡失败! ${new Date().toLocaleString()}`);
};
createJob(success, fail);
CronJob第一个参数定时时间, 共6个分别表示:
- Seconds: 0-59
- Minutes: 0-59
- Hours: 0-23
- Day of Month: 1-31
- Months: 0-11 (Jan-Dec)
- Day of Week: 0-6 (Sun-Sat)
例如0 20 0-11 * * *表示每天0点到11点的0:20执行定时任务, 详细参考 Cron
最后, 执行 node cron, 开始定时.
代码丢到服务器上, 一直跑就行了.
没有服务器可以让自己电脑不休眠. 想要休眠或者经常关机可以创建.bat文件(Windows), 写入
cd /d E:\puppeteer-test\src
node index不用定时了, index中直接执行main, 每次打卡点一下.bat文件一键打卡.