前言
最近想玩一下node的爬虫,发现crawler的爬取功能十分受限,特别是在现在满大街都是vue,angular,react等前端框架写的网页的情况下,crawler就不能爬取js动态生成的内容了,这十分鸡肋。于是,研究了很久,找到了利用chromium作为沙盒环境进行爬虫的puppeteer爬虫框架,他的功能原本是模拟进行自动化测试的,可以捕获页面的截屏,获取页面的内容等,用来做动态内容的抓取,功能十分强大。示例及demo我放在github上了,可自取。
puppeteer介绍
是什么
一句话总结:调用chrome开放的api并且操纵chromium去实现复杂的抓取业务逻辑的js接口封装库。
puppeteer默认以headless模式控制 Chrome浏览器来运行(headless chrome),它的最大优点就是可以爬取SPA页面或者动态渲染的页面内容,还能模拟用户行为。
几个概念:
headless:无头模式,无界面的
headless browser:简易的无GUI界面渲染的browser程序,并暴露一些api给程序调用
headless chrome:无界面的环境下运行chrome浏览器,headless browser的一种
能做什么
- 生成页面 PDF。
- 抓取 SPA(单页应用)并生成预渲染内容(即“SSR”(服务器端渲染))。
- 自动提交表单,进行 UI 测试,键盘输入等。
- 创建一个时时更新的自动化测试环境。 使用最新的 JavaScript 和浏览器功能直接在最新版本的Chrome中执行测试。
- 捕获网站的 timeline trace,用来帮助分析性能问题。
- 测试浏览器扩展。
运行原理
使用DevTools协议与浏览器进行通信
api结构
Puppeteer 使用 DevTools 协议 与浏览器进行通信。
Browser 实例可以拥有浏览器上下文。
BrowserContext 实例定义了一个浏览会话并可拥有多个页面。
Page 至少有一个框架:主框架。 可能还有其他框架由 iframe 或 框架标签 创建。
frame 至少有一个执行上下文 - 默认是JavaScript 被执行的上下文 。
Worker 具有单一执行上下文,并且便于与 WebWorkers 进行交互
puppeteer示例
1. 截图,生成pdf
// 截屏,生成pdf
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: false, slowMo: 0 }); // 启动一个浏览器,headless默认为true
const page = await browser.newPage(); // 打开一个新页面
await page.goto('http://example.com'); // 打开某个网址
await page.screenshot({ path: 'test/image/example.png' }); // 执行截图
// await page.pdf({ path: 'test/pdf/example.pdf', format: 'A4' }); // 创建一个pdf,headless需为true
await browser.close(); // 关闭浏览器
})()
运行结果:
2. 自动提交表单
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: false })
const page = await browser.newPage()
await page.goto('http://www.baidu.com')
await page.type('#kw', 'puppeteer') // 键盘输入关键字
await page.waitFor(1000)
await page.click('#su') // 模拟用户点击搜索提交表单
await page.waitFor(2000)
await page.screenshot({ path: 'test/image/search.png', fullPage: true }) // 截全屏
await browser.close()
})()
运行结果:
3. UI自动化测试
// ui测试
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone = devices['iPhone 6'];
(async () => {
const browser = await puppeteer.launch({ headless: false, slowMo: 0 });
const page = await browser.newPage();
await page.emulate(iPhone); // 让页面模拟成iphone6
await page.goto('https://m.v.qq.com/play.html?cid=rjvr8psrvic4567&vid=')
await page.screenshot({ path: 'test/image/1.png' })
// 点击登录
await page.click('.btn_user_text')
await page.waitFor(1000)
await page.screenshot({ path: 'test/image/2.png' })
// 点击qq登录
await page.waitFor(1000)
await page.click('.btn_qq')
await page.waitFor(1000)
await page.screenshot({ path: 'test/image/3.png' })
// 输入账号密码
await page.type('#u', '******') // 账号
await page.type('#p', '******') // 密码
await page.screenshot({ path: 'test/image/4.png' })
await page.waitFor(1000)
// 点击登录
await page.click('#go')
await page.waitFor(1000)
await page.screenshot({ path: 'test/image/5.png' })
await page.waitFor(1000)
await browser.close();
})()
运行结果:
4. 爬取动态渲染网页内容
// 爬虫
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: false, slowMo: 0 });
const page = await browser.newPage();
await page.setViewport({ // 设置viewport大小
width: 375,
height: 600,
isMobile: true,
hasTouch: true
})
await page.goto('https://www.bilibili.com/');
let list = await page.evaluate(() => { // 爬取内容
const title = document.querySelectorAll('.ri-title')
const elements = Array.from(title);
let titles = elements.map(element => {
return element.innerHTML
})
return titles
});
console.log(list)
await page.waitFor(1000) // 等待时长
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await browser.close();
})()
运行结果:
5. 分析页面性能
// 性能分析
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone = devices['iPhone 6'];
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulate(iPhone); // 让页面模拟成iphone6
await page.tracing.start({ path: 'test/doc/trace.json' }); // 生成页面性能追踪的文件
await page.goto('https://www.bilibili.com/');
await page.tracing.stop();
browser.close();
})();
获取到的json文件导入chrome的开发者工具的performance下,可以得到页面性能分析图表。
6. 创建不同版本的chrome进行测试
const browserFetcher = puppeteer.createBrowserFetcher();
const revisionInfo = await browserFetcher.download('533271');
const browser = await puppeteer.launch({executablePath: revisionInfo.executablePath})
总结
在项目开发中,如果直接由前端就能完成爬虫任务的话,使用puppeteer不失为一个好的选择,这样既可以免去前后端的对接时间,又能更高效开发;对于测试人员而言,使用puppeteer进行自动化测试,也能节省很多重复性工作。
参考文章
- puppeteer文档
- 实例:使用puppeteer headless方式抓取JS网页
- Node 使用 puppeteer 抓取动态页面