按:
转了一圈,发现目前搭配 puppeteer-record
是最省心的。那就重新收拾一下。2020-8-11
初次更新 2019-11-15
Puppeteer Recorder 近期用过 puppeteer 可以记录操作
再次更新 2020-12-23:
看了文章 《效率提高十倍,Puppeteer 如何启动交互模式?》,说是 node.js v14 可以 node -i
进入交互环境,然后再chrome的控制台找到链接入口,选择 connect
然后可以进行交互环境了,可以写一行看一行代码了,这挺方便的。
import puppeteer from 'puppeteer-core'
const getDefaultOsPath = () => {
if (process.platform === 'win32') {
return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
} else {
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
}
}
let browser = await puppeteer.launch({
executablePath: getDefaultOsPath()
}))
相关资源
文档和工具:
- 官方文档,现在免翻墙了 https://pptr.dev/
- 一个官方认可的第三方中文文档 中文文档
- 可以在线使用puppeteer 需要翻墙
- 强烈推荐的 辅助工具 record https://checklyhq.com/docs/puppeteer-recorder/
辅助阅读材料:
- 一篇不错的入门文章,学到了很多 http://csbun.github.io/blog/2017/09/puppeteer/
- 一篇不错的文章 https://juejin.im/post/5af6876b518825426726218f
- 一个封装工具 可暂时忽略 https://sdk.apify.com/
前置概念
Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行,但是可以通过修改配置文件运行“有头”模式。
- 生成页面 PDF。
- 抓取 SPA(单页应用)并生成预渲染内容(即“SSR”(服务器端渲染))。
- 自动提交表单,进行 UI 测试,键盘输入等。
- 创建一个时时更新的自动化测试环境。 使用最新的 JavaScript 和浏览器功能直接在最新版本的Chrome中执行测试。
- 捕获网站的 timeline trace,用来帮助分析性能问题。
- 测试浏览器扩展。
下载会失败 。在安装 puppeteer
的时候会自动下载一个 Chromium,但网址被墙没办法下载,需要先安装中国镜像源,具体见下方。后来推出了 puppeteer-core
包,不在捆绑下载浏览器,可以使用电脑自带的chrome,这样只要保证本机的chrome版本不要太低就可以。
安装
刚才提到需要配置国内源,补充:核心是修改 项目中 .npmrc
puppeteer-download-host=https://npm.taobao.org/mirrors
使用 cnpm
:
# npm i -g mirror-config-china --registry=https://registry.npm.taobao.org
# 检查是否安装成功
npm config list
npm i puppeteer
# puppeteer-download-host=https://npm.taobao.org/mirrors
# 如果始终安装失败,使用 cnpm i
npm i puppeteer
cnpm i puppeteer
也可以考虑 puppeteer-core
,这样没有自带浏览器,那就需要在项目启动的时候设置chrome路径:
npm i puppeteer-core
代码如下:
const getDefaultOsPath = () => {
if (process.platform === 'win32') {
return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
} else {
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
}
}
const getDefaultOsPath = require('./getDefalutOsPath')
module.exports = {
executablePath: getDefaultOsPath(),
userDataDir: 'test-profile-dir',
headless: false,
defaultViewport: {
width: 1600,
height: 900
},
args: [
'–disable-gpu',
'–disable-dev-shm-usage',
'–disable-setuid-sandbox',
'–no-first-run',
'–no-sandbox',
'–no-zygote',
'–single-process'
]
}
跑一个demo
创建个 demo1.js 来试跑一下。
// demo1.js
const puppeteer = require('puppeteer-core');
(async () => {
const browser = await puppeteer.launch(launchOptions);
const page = await browser.newPage();
await page.goto("http://www.baidu.com");
await page.screenshot({ path: "baidu.png" });
browser.close();
})();
bash页面执行:
node demo1.js
如果没有报错的话,就是打开了浏览器,并截图。这样就安装成功了。
代码文件命名为 demo1.js 可以在目录里查找。
如果下载有问题,就可以考虑使用本地的chrome,参考上面。
这样就掌握基本的用法了。
经验技巧
recored插件
利用 puppeteer-record
插件,可以快速生成一些基础代码,必备。
sleep函数
// 延时器
let timeout = function (delay) {
console.log('延迟函数:', `延迟 ${delay} 毫秒`)
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve(1)
} catch (error) {
reject(error)
}
}, delay);
})
}
Linux下缺少依赖
有可能在Linux下因为缺少依赖,安装不上,考虑安装下面的依赖文件:
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
Linux 下中文不支持
考虑安装以下依赖,来支持中文,避免乱码:
sudo apt-get install -y --force-yes --no-install-recommends fonts-wqy-microhei ttf-wqy-zenhei
模拟手机
模拟手机打开页面
const puppeteer = require("puppeteer");
const devices = require("puppeteer/DeviceDescriptors");
const iPhone6 = devices["iPhone 6"];
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulate(iPhone6);
await page.goto("http://baidu.com");
await page.setViewport({ width: 1280, height: 800 });
拦截请求
不想加载图片和js,想让页面打开更快。可以考虑拦截
// 页面设置拦截
await page.setRequestInterception(true);
// 过滤拦截
page.on("request", interceptedRequest => {
// 符合匹配就 abort ,其他 continue
// 这里是操作的 url 字符串
if (
interceptedRequest.url().endsWith(".png") ||
interceptedRequest.url().endsWith(".jpg") ||
interceptedRequest.url().endsWith(".js") ||
["image", "script"].indexOf(interceptedRequest.resourceType()) !== -1
) {
interceptedRequest.abort();
} else {
interceptedRequest.continue();
}
await page.goto("http://www.wangxiao.cn/cfe/dt_bm/");
//demo2
const browser = await puppeteer.launch();
const page = await browser.newPage();
const resList = []
await page.setRequestInterception(true);
page.on('request', request => {
if (request.resourceType() === 'image')
// request.abort();
resList.push(request.url())
request.continue();
// else
// request.continue();
});
await page.goto('https://news.google.com/news/');
// await page.screenshot({ path: 'news.png', fullPage: true });
await browser.close();
获取页面源代码
await page.goto("https://www.youtube.com", { waitUntil: "networkidle0" });
const html = await page.content();
屏幕滚动
解决懒加载和数据滚动加载,这就是我认为 puppeteer 最有魅力的地方了。
以往只能去慢慢分析页面结构、研究接口api ,来获取异步加载的数据,现在直接无脑模拟浏览器,滚动页面来获取页面内容。
await page.goto("https://www.jd.com/", {
waitUntil: "networkidle2"
});
await autoScroll(page);
async function autoScroll(page) {
await page.evaluate(async () => {
await newPromise((resolve, reject) => {
var totalHeight = 0;
var distance = 200;
var timer = setInterval(() => {
var scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= scrollHeight) {
clearInterval(timer);
resolve();
}
}, 200);
});
});
}
操作页面
在浏览器中使用自己的js 这里好像没办法传入自己的function
const result = await page.$eval("#hotwords", el => {
return el.innerText;
});
// 或者
const result = await page.evaluate(() => {
let element = document.querySelector("#hotwords");
return element.innerText;
});
键盘方法
await page.type("#search input", "macbook pro", {
delay: 300
});
await page.keyboard.press("Enter");
// 或者
const inputEle = await page.$("#search input");
await inputEle.type("macbook pro", {
delay: 300
});
键盘大写、快捷键
await page.focus("input");
await page.keyboard.down("Shift");
await page.keyboard.press("KeyM");
await page.keyboard.up("Shift");
等待节点
想等到某个元素出现
await page.waitForSelector('div.title > a'); //等待节点出现
launch 选项
headless: false
slowMo: 200 减速显示,有时候为了模拟人会特意减速
devtools: true 显示dev工具
页面中的iframe
如果网页内有用iframe等标签,这时page对象是无法读取 iframe 里面的内容的,需要用到page.frames()。返回一个Frame对象数组。 通常iframe会有name属性,判断name属性可以快速获取单个Frame对象的内容。
let iframe = await page.frames();
iframe.find(f => f.name() === 'name')
监听 console的输出
想看页面里的console,可以监听
page.on('console',msg => console.log(msg.text());
await page.evaluate(() =>{
console.log()
})
使用 coverage trace
可以使用 chrome自带的分析工具来分析页面
const puppeteer = require("puppeteer");
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
//start coverage trace
await Promise.all([
page.coverage.startJSCoverage(),
page.coverage.startCSSCoverage()
]);
await page.goto("https://www.cnn.com");
//stop coverage trace
const [jsCoverage, cssCoverage] = await Promise.all([
page.coverage.stopJSCoverage(),
page.coverage.stopCSSCoverage()
]);
let totalBytes = 0;
let usedBytes = 0;
const coverage = [...jsCoverage, ...cssCoverage];
for (const entry of coverage) {
totalBytes += entry.text.length;
for (const range of entry.ranges)
usedBytes += range.end - range.start - 1;
}
const usedCode = ((usedBytes / totalBytes) * 100).toFixed(2);
console.log("Code used by only", usedCode, "%");
await browser.close();
})
}
const puppeteer = require("puppeteer");
const devices = require("puppeteer/DeviceDescriptors");
const iPhonex = devices["iPhone X"];
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulate(iPhonex);
//start the tracing
await page.tracing.start({ path: "trace.json", screenshots: true });
await page.goto("https://www.bmw.com");
//stop the tracing
await page.tracing.stop();
await browser.close();
})();
简言之,在goto页面之前,打开特定的功能
监控页面
监控页面。写一个app.js setInerval 每隔5分钟运行一次,截图页面,如果有问题可以存下来,再说其他task
也可以使用 node-cron 来制作定时任务
关于新开页面
如果a链接,新开了页面。
如果a标签target属性为_blank,新开了页面,可以使用let pages = await browser.pages(),它返回当前的页面的page类数组集合,想要哪个页面数组中拿就行,不过原来的page还是一直指向原来的页面。
wsl 使用
Hi! Here is how I got it running under WSL:
install chromium browser through apt-get
$ sudo apt-get install chromium-browser
then:
const browser = await puppeteer.launch({executablePath:’/usr/bin/chromium-browser’});