无头浏览器

一直知道一般的批量操作不会在图形化界面中的浏览器里面进行,更多的是使用 无头浏览器 来对网页进行批量操作。正好最近工作有接触到,那么就来聊一下这个 无头浏览器

Puppeteer

Puppeteer是由Google开源的一款无头浏览器,Puppeteer因为是一个npm的包,所以安装很简单:

1
npm i puppeteer

访问页面

Puppeteer类似其他框架,通过操作Browser实例来操作浏览器作出相应的反应。

1
2
3
4
5
6
7
8
9
const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://www.baidu.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();

在命令行中 node index.js ,即可开启一个无界面的浏览器并且将其中的内容截屏保存为example.png的图片。

这里我们可以看到首先通过puppeteer的launch()方法生成了一个browser的实例,其次调用了newPage()的方法然后打开了一个网页。

执行脚本

另外就是如果想执行自己的代码则可以通过page.evaluare()来进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://www.baidu.com');

const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio
};
});

console.log('Dimensions:', dimensions);
// Dimensions: { width: 800, height: 600, deviceScaleFactor: 1 }
await browser.close();
})();

上方的代码就是使用page.evaluate()的方法去获取了整个页面的viewport,需要注意的是page.evaluate()的方法不能使用全局 / 外部的变量,任何需要使用的外部变量都需要作为参数传入函数内,想要的执行结果也必须要return出来。

另: 可以通过const browser = await puppeteer.launch({headless: false})传入haedless来控制是否显示界面。

提交表单

一个网页中比较高级的交互功能和信息的获取会用到很多提交表单的操作,在Puppeteer中提交表单也很简单。

1
2
3
await page.focus('#input');
await page.type('test-input');
await page.press('Enter');

有一点需要注意的是,在无图形化界面的情况下有关DOM的操作需要等Dom渲染完毕之后才能进行操作。

识别无头浏览器

因为一般情况下用户均使用的是图形化界面的浏览器,所以在SDK的埋点中检测出页面是否由无头浏览器打开也是web安全策略中的一环。

userAgent

通过window.navigator.userAgent来获取用户的浏览器种类, 在Puppeteer中会有明显的区别

navigator: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/69.0.3452.0 Safari/537.36’

HeadlessChrome 会在Puppeteer中出现,不过这种情况非常容易伪造 不能单靠这个属性来进行判断。

插件 / languages

在无头浏览器中一般用户都不会去下载插件所以可以通过插件的数量从侧面看出来用户是否处于无头浏览器的环境下navigator.plugins.length

另外实测在除了IE下的所有浏览器中使用navigator.languages返回的都是一个数组成员都 >= 2, 而在Puppeteer中则会返回只有一个语言的数组。也可以根据这个来从侧面判断一下。

hi you can see me