Puppeteer三部曲:
前言
在上两篇有关于Puppeteer文章的介绍中,分别介绍了他的理论以及爬虫的实战操作。但是上一篇的实战仅仅只是简单的操作。而在我们实际使用过程中,我们是先需要登录网站,然后再进行一系列的操作流程的,在上一篇文章中,其实是不用登录的。
而在这篇文章中,我们主要介绍如何利用Puppeteer,实现网站系统的登录的。
我们知道简单的网站,只需要输入账号密码即可完成登录,并且我们在本地开发过程中,也可以设置测试环境不需要验证码的流程来实现系统的登录,这样实现起来就容易的多了。
但是我们也知道,实际的网站登录校验方案可能会因网站的登录机制、安全性要求以及业务需求而有所不同,仅仅输入用户名和密码是无法完成登录的,以下是一些常见的网站登录校验方案:
图片滑动验证图片验证码输入,或者图片计算输入图片拼图,点击文字相关的图片手机验证码校验等等
而我们本篇文章主要是介绍如何绕过这些登录校验方案,来实现网站的登录。
输入账号密码登录
这一节讲述如何
通过输入用户名和密码完成登录,不涉及点击登录后的校验。
对于没有验证码的网站,基本上就是输入账号,输入密码,点击登录这三步,需要用到page对象的两个方法:type用来在输入框中输入信息,click用来点击登录。
使用 Puppeteer 只输入账号和密码并登录一个网站,通常步骤如下:
- 启动浏览器和新建页面。
- 导航到登录页面。
- 等待账号和密码输入框加载完成。
- 输入账号和密码。
- 提交表单。
现在很多的网站都需要登录后进行校验的,只有一些自己做的小网站没有校验措施。这里我找了好久,才找到一个书签网站,他是只需要输入手机和密码就能完成登录的。当然自己开发的项目也可以在测试环境设置,但是就不方便公开了。这里以书签网站进行举例:闪击地球
而且这里有一个特殊情况:网站的登录页面是通过
iframe进行镶嵌的,所以在进行实际的用户名密码的输入过程和表单提交过程。都是在iframe页面进行操作的。如果你的登录系统不是iframe进行镶嵌的,可忽略中间的iframe过程。
废话少说,我们根据上述的步骤来实现系统登录:
启动浏览器和新建页面
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
打开登录页面
const navigationPromise = page.waitForNavigation();
await page.goto("https://nav.sankki.com/#/index");
await page.waitForSelector(
".page-index > #home > .view-index__top > .view-index__top-inner > a:nth-child(4)"
);
await page.click(
".page-index > #home > .view-index__top > .view-index__top-inner > a:nth-child(4)"
);
await navigationPromise;
这个可以通过插件来生成,详细操作请看上一篇文章。
这里的登录页面是个iframe,我们要获取iframe页面,并在这个页面进行操作。
获取iframe的框架内容
const frames = await page.$$("iframe");
const frame = await frames[0].contentFrame(); // 获取iframe的框架内容
await new Promise((resolve) => setTimeout(resolve, 500)); // 等待0.5s,防止后面元素获取不到
这里是由于iframe页面是没有类和id的,我只能通过标签进行查找。
输入账号和密码
await frame.type(`input[placeholder="手机号"]`, "********");
await frame.type(`input[placeholder="密码"]`, "*********");
等待并点击登录按钮
await frame.click(".m-login__submit .is-primary");
完成上述的操作,即可实现网站的登录。
完整代码如下:
const puppeteer = require("puppeteer");
const CHROME_EXECUTALBE_PATH =
"C:/Program Files/Google/Chrome/Application/chrome.exe";
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: {
width: 1360,
height: 720,
},
args: ["--start-maximized"],
executablePath: CHROME_EXECUTALBE_PATH, // 这个路径写上就可以执行了
});
const page = await browser.newPage();
const navigationPromise = page.waitForNavigation();
await page.goto("https://nav.sankki.com/#/index");
await page.waitForSelector(
".page-index > #home > .view-index__top > .view-index__top-inner > a:nth-child(4)"
);
await page.click(
".page-index > #home > .view-index__top > .view-index__top-inner > a:nth-child(4)"
);
await navigationPromise;
const frames = await page.$$("iframe");
const frame = await frames[0].contentFrame(); // 获取iframe的框架内容
// await downloadImage(page1, lastSpan);
await new Promise((resolve) => setTimeout(resolve, 500));
await frame.type(`input[placeholder="手机号"]`, "******");
await frame.type(`input[placeholder="密码"]`, "******");
await frame.click(".m-login__submit .is-primary");
})();
登录掘金,实现签到
掘金的登录是有登录校验的,用户在输入完毕用户名和密码后,会出现图片滑动校验。
图片滑动校验的难点是如何找到被挖出来的图片的准确位置,一旦得到准确位置,我们就可以模拟鼠标操作,进而来跳过验证,实现登录。
这里有一些文章是专门讲述这些的:
大家如果有想了解的,可以自行阅读。
就像上述文章讲到的一样,这种校验会会越来越严格,方式也不是一成不变的,可能你这段时间搞定了这个方法,后面人家就又升级了,还要重新搞。
验证码的安全性是在用户体验和安全性之间的一个平衡,如果安全性太高,用户体验就会变差,如果用户体验太好,安全性就会变差。
这里我们要清楚一点,用Puppeteer写脚本,是为了在系统中进行繁琐的操作的。因此登录进去就行,不必在登录的过程中耗费这么多精力,只要能快速登录就可以了。
因此在本文中,利用一个取巧的笨方法:点击登录后,会出现滑动图片验证,然后我们可以延迟一段时间,让用户来进行滑动图片的校验,然后我们就可以登录进去进行一系列的操作了。
这里有两个关键点:
-
登录后,滑动图片延迟:
await page.waitForSelector( ".login-body > .login-main > .panel > .button-group > .btn-login" ); await page.click( ".login-body > .login-main > .panel > .button-group > .btn-login" ); // 延迟操作,用户实现验证过程 await new Promise((resolve) => setTimeout(resolve, 5000)); -
登录成功后,页面会进行加载,我们后面一系列的操作,是在页面加载之后进行的,否则会拿不到对应的元素。那页面加载是如何监听的呢?
page.waitForNavigation()方法可以等待页面导航完成。您可以使用以下代码等待页面加载完成:await page.waitForNavigation({ waitUntil: "load", });其中,
waitUntil参数可以指定等待的条件,例如:load: 等待页面加载完成,包括所有资源(例如图像、脚本等)都已加载。domcontentloaded: 等待 DOMContentLoaded 事件触发,表示页面中的所有 DOM 元素都已加载。networkidle0: 等待网络空闲时间超过 500 毫秒,表示页面中没有正在进行的网络请求。networkidle2: 等待网络空闲时间超过 500 毫秒,并且没有正在进行的网络请求。
注意:
page.waitForNavigation()方法只能在页面导航之后使用。- 如果页面导航失败,则会抛出错误。
这样我们就可以根据上一章的步骤和上面讲的要点来实现一个简单的登录掘金,实现签到的脚步:
const puppeteer = require("puppeteer");
const CHROME_EXECUTALBE_PATH =
"C:/Program Files/Google/Chrome/Application/chrome.exe";
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: {
width: 0,
height: 0,
},
args: ["--start-maximized"],
executablePath: CHROME_EXECUTALBE_PATH, // 这个路径写上就可以执行了
});
const page = await browser.newPage();
const navigationPromise = page.waitForNavigation();
await page.goto("https://juejin.cn/");
await page.waitForSelector(
".nav-list > .right-side-nav > .nav-item > .login-button-wrap > .login-button"
);
await page.click(
".nav-list > .right-side-nav > .nav-item > .login-button-wrap > .login-button"
);
await page.waitForSelector(
".auth-body > .login-body > .login-main > .other-login-box > .clickable"
);
await page.click(
".auth-body > .login-body > .login-main > .other-login-box > .clickable"
);
await page.type(
`input[placeholder="请输入邮箱/手机号(国际号码加区号)"]`,
"*********"
);
await page.type(`input[placeholder="请输入密码"]`, "******");
await page.waitForSelector(
".login-body > .login-main > .panel > .button-group > .btn-login"
);
await page.click(
".login-body > .login-main > .panel > .button-group > .btn-login"
);
// 延迟操作,用户实现验证过程
await new Promise((resolve) => setTimeout(resolve, 5000));
// 等待页面导航完成
await page.waitForNavigation({
waitUntil: "load",
});
await page.waitForSelector(
".index-aside > .signin-tip > .first-line > .btn > .btn-text"
);
await page.click(
".index-aside > .signin-tip > .first-line > .btn > .btn-text"
);
await navigationPromise;
await page.waitForSelector(
".signin > .content-body > .content-right > .code-calender > .signedin"
);
await page.click(
".signin > .content-body > .content-right > .code-calender > .signedin"
);
await page.waitForSelector(
".success-modal > .byte-modal__wrapper > .byte-modal__content > .byte-modal__header > .byte-modal__headerbtn > .byte-icon"
);
await page.click(
".success-modal > .byte-modal__wrapper > .byte-modal__content > .byte-modal__header > .byte-modal__headerbtn > .byte-icon"
);
})();
其他的验证方法也可以参考这种方案。
设置token或者cookie,实现系统登录
还有一种方案是通过先登录系统,然后保存对应的token或者cookie,然后在页面进去的时候,来设置页面的token或者cookie,然后刷新页面即可实现登录。
在使用 Puppeteer 进行这种方式的登录时,可以通过以下步骤实现:
- 登录并获取 token 或者 cookie: 在 Puppeteer 中模拟用户登录系统,然后获取登录成功后返回的 token 或者 cookie 信息。
- 在后续页面设置 token 或者 cookie: 在后续需要登录状态的页面访问之前,通过 Puppeteer 的
setExtraHTTPHeaders方法来设置请求的 headers,或者使用page.setCookie方法来设置页面的 cookie 信息,从而保持登录状态。
下面是一个简单的示例代码,演示了如何使用 Puppeteer 来设置 token 或者 cookie 来实现持续的登录状态:
const puppeteer = require("puppeteer");
(async () => {
// 启动浏览器
const browser = await puppeteer.launch({ headless: false }); // 设置 headless: false 以便看到浏览器操作过程
const page = await browser.newPage();
// 设置页面的令牌或Cookie
await page.setExtraHTTPHeaders({
Authorization: `Bearer ${token}`,
// 或者设置Cookie
Cookie: `session=${cookies.session}`,
});
// 导航到需要登录状态的页面
await page.goto("https://example.com/dashboard"); // 替换为实际需要登录的页面 URL
// 刷新页面,确保登录状态生效
await page.reload();
// 进行其他操作...
// 关闭浏览器
await browser.close();
})();
在这个示例中,我们通过 page.setExtraHTTPHeaders 方法来设置请求的 headers,将 token 添加到请求的 Authorization 头部中,从而实现登录状态的持续。
但是这种方案,首先你要熟悉网路登录的原理,如果是自己写的项目,这种方案是可以的。但是如果是别的项目,探究原理就很麻烦,而且登录成功后,它的cookies和token里面的东西很多,也很难区分是哪个或者哪些是用来登录检验的。因此这种方案也是
不推荐的。