一、安装和使用
1.1安装
cnpm i -S puppeteer
用cnpm安装没有试过报错,默认会下载puppeteer配套版本的Chromium。
1.2 使用
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false, //默认为true(无头),不显示浏览器界面
slowMo :200, //减速显示,有时会作为模拟人操作特意减速
devtools: true //显示开发者工具。页面宽高默认800*600,把开发者工具显示再隐藏页面会占满屏幕,有没有大佬解释下?
});
//生成Page对象
//const page = await browser.newPage();//官网写法:一打开浏览器会打开两个tab,第二个才是你正在操作的tab
const page = (await browser.pages())[0]; //这是我的写法,只有一个tab
await page.goto('https://www.juejin.com'); //跳转到掘金
//请开始你的表演...
await browser.close(); //关闭浏览器
})();
puppeteer基本上每个操作都会返回一个Promise,记得要用await接收下一步的操作。
puppeteer上也提供了一些第三方写的爬虫demo,不过感觉封装得有点多,先不玩。
二、基本用法
2.1 调整页面
页面宽高默认800*600,我觉得太小。我一般都会先初始化页面大小。
和上面说的一样,这只是初始化的时候的大小,当打开再隐藏开发者工具后,页面就会占满全屏了,不知道这是不是bug。
await page.setViewport({
width: 1280,
height: 800
});
2.2 模拟输入和点击
目测底层是用document.querySelector()
await page.type(selector, 'Hello puppeteer'); //找到对应的选择器然后填充值。如果之前设置了slowMo会看到像人打字一样,值是一个一个填进<input/>
await page.click(selector); //模拟点击,这对传统异步分页(url没有分页参数)很有用,selector定在下一页的标签上
2.3 iframe处理
如果网页内有用iframe等标签,这时page对象是无法读取<iframe>
里面的内容的,需要用到page.frames()
。返回一个Frame对象数组。 通常iframe会有name属性,判断name属性可以快速获取单个Frame对象的内容。
let iframe = await page.frames();
iframe.find(f => f.name() === 'name')
2.4 waitFor函数
waitFor函数是简写,Page和Frame对象都有。我只会用以下两种方式,剩下了请大佬指点一下。
为了简化,Page和Frame对象都有的api,我不会再特意说明,会在代码中直接体现。
await iframe.waitFor('.contain .item') //在<iframe>中等待'.contain .item'的节点出现,阻塞结束(ps:优先使用,有时200ms我是等不起的)
await page.waitFor(200)//页面等待200ms
2.5 selector和emulate
为什么要合在一点写呢?因为确实有一个组合的api叫eval
先分开说说吧。
2.5.1 selector
目测底层是用document.querySelector()和document.querySelectorAll()。熟悉这两个api的人应该很容易上手。
//ps:较少用
page.$(selector) // document.querySelector()
iframe.?(selector) // document.querySelectorAll(), ?是All的意思
2.5.2 emulate
这里首先要有个概念,puppeteer爬虫解析dom在浏览器,这个api的实参在浏览器中。所以可以在这个函数内进行dom操作,同时本地的node api是无法在这里运行的,运行console.log(global)
会报错。
举个栗子:在函数内有console.log('按f12,我出现在浏览器的console中,并不在node命令行')
你会发现node命令行看不到这句话,而在Chromium的console中看见。因此你应该理解他的运行环境是当前网站,而不是你本地的node。
//ps:更少用
await page.evaluate(el => {
//喜闻乐见的dom操作
})
await iframe.evaluate(el => {
//请开始你的表演
console.log('按f12,我出现在浏览器的console中,并不在node命令行')
})
2.5.3 真正的主角 $eval
和 ?eval
上面的两个api一起用就变成了eval,我最常用的api之一。一个api顶上面两个,集中一起写,舒服。
const result = await page.$eval(selector, el => {
//如果需要赋值要返回Promise
return new Promise(resolve => {
//...一波骚操作
//可以用Dom api啦
reslove(obj)
})
});
await iframe.$$eval(selector, el => {...});
2.6 监听事件
上文说过在page.evaluate
中用console
是不能在node命令行打印出来的,不过有了监听事件就可以改变这个规则了。也可以在监听事件里面做容错处理。
page.on('console', msg => {
console.log(msg);
});
个人觉得如果打印dom的话,还是看浏览器的console比较好,直观。
//监听浏览器报错
page.on('pageerror', pageErr => {
console.log(pageErr);
});
//监听node报错
page.on('error', err => {
console.log(err);
});
三、伪装移动端
const devices = require("puppeteer/DeviceDescriptors");
const iPhone = devices["iPhone 6"];
await page.emulate(iPhone);
使用puppeteer.connect链接已有Chrome浏览器
因为puppeteer.launch启动的Chrome浏览器,虽然可以传浏览器启动参数,但是不知道怎么都没能奏效,
而且关闭后又不能记住参数配置,真是头疼,上网各种搜索,也是没能解决。
于是 ,尝试换了一个思路,就是通过加载一个已经有的浏览器,这个已经安装的浏览器,可以在快捷方式中加启动参数,亲测这个有效。或者直接先打开浏览器,然后,手动配置好了,再启动puppeteer通过调试端口就可以了,算是一种绕路,解决了目前的需求。
- 现在Chrome的桌面快捷方式中添加调试启动参数。 方法:快捷方式–右键属性–目标 在最后添加 --remote-debugging-port=9222即可,和.exe之间有个空格。
- 在浏览器中请求地址:http://`localhost:9222/json/version` 是正常的GET请求,获得到webSocketDebuggerUrl参数。
- 用
const browser = await puppeteer.connect({
browserWSEndpoint: webSocketDebuggerUrl
});
即可。
docker 环境的话,最佳做法难道不是再搭一个 browserless/chrome ,然后装 puppeteer-core 连过去就行
puppeteer与puppeteer-core
- puppeteer 安装这个版本的过程中,将下载 Chromium (~170MB Mac, ~282MB Linux, ~280MB Win)
- puppeteer-core 这个版本不会下载 Chromium 除了是否下载 Chromium 的区别外,另一个区别就是,puppeteer-core 会忽略所有的 PUPPETEER_* 环境变量。
到底应该使用哪个版本?按照极客思维,应该选择小的、简单的那个,然而官方一句话:在大多数情况下,使用 puppeteer 是不错的(In most cases, you’ll be fine using the puppeteer package. )让我认为,官方建议使用 puppeteer 版本,除非特殊情况。