关键词:自动化测试 爬虫 chrome
文档 EN: pptr.dev/ CN: puppeteer.bootcss.com/
Puppeteer 通常用在自动化测试和爬取页面,提供丰富 api 来控制 headless Chrome 或者 Chromium。
通过这篇文章你会了解到:
- puppeteer 环境安装
- puppeteer 基本使用
- 滚动截屏
- 模拟点击获取数据
- 定时任务获取数据
安装
npm 包比较大,推荐全局安装。
npm i puppeteer # or using yarn yarn add puppeteer # or using pnpm pnpm i puppeteer
使用
环境配置
pnpm init -y pnpm i puppeteer touch demo.js
刚开始先通过 node demo.js
运行项目。
官方案例
puppeteer.launch
创建 Browser 实例,类似打开浏览器browser.newPage
创建 Page 实例,类似浏览器中新开页面browser.close()
关闭浏览器。
const puppeteer = require("puppeteer"); (async () => { // 1. 创建了Browser实例,类似打开浏览器 const browser = await puppeteer.launch({ headless: "new", }); console.log("打开浏览器"); // 2. 创建了Page实例,类似浏览器中新开页面 const page = await browser.newPage(); console.log("打开页面"); // 导航到要访问的页面 await page.goto("https://www.baidu.com"); await page.screenshot({ path: "example.png" }); console.log("success"); // 在页面中执行脚本 Get the "viewport" of the page, as reported by the page. const dimensions = await page.evaluate(() => { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio, }; }); console.log("Dimensions:", dimensions); // 设置可视区域 await page.setViewport({ width: 1420, height: 1000, }); // 全屏截取 await page.screenshot({ path: "example1.png", fullPage: true, }); // waitUntil 来配置什么时候导航结束 "domcontentloaded"、"networkidle0"、"networkidle2" await page.goto("https://news.ycombinator.com", { waitUntil: "networkidle2", }); await page.pdf({ path: "hn.pdf", format: "A4" }); console.log("success pdf"); // 3. 关闭浏览器 await browser.close(); })();
page.goto
用于导航到新页面,waitUntil
是指定一个等待条件,告诉 puppeteer 合适导航完成。默认取值 load, 还有 documentloaded
等待页面 DOMcontentLoaded
事件触发。
page.evaluate
功能类似 eval
,在浏览器中执行脚本,结果返回程序。
page.$(selector)
就是 document.querySelector,而 page.$$(selector)
则是 document.querySelectorAll.
这个 case 仅仅是整合了官方最简单的三个案例。运行一遍,有个概念。
拓展 - 滚动截屏
很多网站数据是懒加载的,如果想要把所有的数据获取到,则需要一些相关的元素操作,比如滚动。
const puppeteer = require("puppeteer"); (async () => { const browser = await puppeteer.launch({ headless: "new", }); console.log("打开浏览器"); const page = await browser.newPage(); console.log("打开页面"); // demo 懒加载及数据滚动加载 await scrollLoad(page); await browser.close(); })(); // 懒加载及数据滚动加载 async function scrollLoad(page) { await page.goto("https://www.jd.com/", { waitUntil: "networkidle2", }); // setViewport setUserAgent // await page.emulate(puppeteer.KnownDevices['iPhone 13 Pro Max']); await page.emulate({ viewport: { width: 375, height: 667, isMobile: true, }, userAgent: '"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"', }); // 获取element内容 const result = await page.$eval("#hotwords", (el) => { return el.innerText; }); console.log("page.$eval", result); // element 输入 const inputEle = await page.$("#search input"); await inputEle.type("macbook pro", { delay: 300, }); // element点击 // const btnEle = await page.$("#search button"); // await btnEle.click(); await autoScroll(page); //全屏截取 await page.screenshot({ path: "jd-all.png", fullPage: true, }); } // 工具函数 - 自动滚动 async function autoScroll(page) { await page.evaluate(async () => { await new Promise((resolve) => { let totalHeight = 0; const distance = 200; const timer = setInterval(() => { const scrollHeight = document.body.scrollHeight; window.scrollBy(0, distance); totalHeight += distance; if (totalHeight >= scrollHeight) { clearInterval(timer); resolve(); } }, 200); }); }); }
拓展 - 点击按钮获取数据
B 站有个换一换的按钮,我们就拿这个功能举例:
首先就是大致一样的页面逻辑:
const puppeteer = require("puppeteer"); const fs = require("fs-extra"); async function demo() { const browser = await puppeteer.launch({ headless: "new", }); console.log("打开浏览器"); const page = await browser.newPage(); console.log("打开页面"); // 页面操作 await dealLogically(page, browser); } demo();
然后再进行业务定制:
async function dealLogically(page, browser) { await page.setViewport({ width: 1920, height: 1080 }); // 打开目标页面 await page.goto("https://www.bilibili.com/"); // 返回的data let data = []; // 获取按钮 const change = await page.$(".feed-roll-btn > .roll-btn"); // 点击10次 for (let i = 0; i < 10; i++) { // 模拟点击事件 change && (await change.click()); // 每次点击之后,留出1s给页面资源加载 await page.waitForTimeout(1000); data = await page.evaluate((data) => { const titles = document.querySelectorAll( ".container .feed-card .bili-video-card__info--tit" ); const imgs = document.querySelectorAll( ".container .feed-card .bili-video-card__cover img" ); const info = document.querySelectorAll( ".container .feed-card .bili-video-card__info--owner" ); titles.forEach((v, i) => { const [owner, date] = info[i].textContent.split("· "); data.push({ img: imgs[i].getAttribute("src"), title: v.textContent, info: { owner, date, }, }); }); return data; }, data); } // 关闭浏览器实例 await browser.close(); // 将数据作为 JSON 写入json文件 const jsonData = JSON.stringify(data, null, 2); //写入文件路径 const filePath = `./${+new Date()}.json`; fs.writeFile(filePath, jsonData, (err) => { if (err) { console.error(err); return; } console.log("Data written to file"); }); }
拓展 - 定时任务
引入 node-schedule 这个包。
npx nodemon index
运行项目。
const schedule = require("node-schedule"); // 引入上面b站的案例代码 const demo = require("../demo"); function main() { schedule.scheduleJob(`自动爬虫任务`, `30 20 * * * *`, ()=>demo()); } main();
拓展 - 模拟登录
通过 cookie 模拟已登录环境,推荐使用 "Export cookie JSON file for Puppeteer" 这个 chrome 插件直接获取cookie 信息。
举例:模拟登陆掘金
const puppeteer = require("puppeteer"); // 通过 插件 获取的 JSON化 cookie const cookieObjects = require("./cookies.json"); (async () => { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); cookieObjects.forEach((cookie) => { page.setCookie(cookie); }); await page.goto("https://juejin.cn"); })()