Puppeteer(词的原意是操纵木偶)是一个通过DevTools Protocol控制headless chrome或chromium的node库,也可以通过配置来使用(non-headless)chrome或chromium。
chromium是谷歌开源的web浏览器项目,chrome是基于chromium开发的。
Puppeteer可以做什么?用户在浏览器中手动执行的大部分操作都可以通过puppeteer完成。
安装puppeteer
时会下载一个和puppeteer提供的API兼容的最新版本的chromium。如果不想默认下载chromium,可以安装puppeteer-core
,这是一个轻量级的puppetter,是通过启动现有的已安装的浏览器或连接一个远程的浏览器,需确保是互相兼容的。
|
|
Puppeteer和其他浏览器测试框架一样,创建一个Browser实例,打开页面,通过API操作页面。
一个简单的Puppeteer样例:
|
|
上面代码实现的是访问网页并截图保存。运行命令node example.js
,就可以看到当前目录下生成的截图图片。
Puppeteer对抗检测
Puppeteer开发的初衷其实是用来做测试,而不是给坏人用。
想要检测Puppeteer模拟器,很直接的就是去看request headers
和浏览器运行时window
的属性,但是请求头和静态属性又容易模拟,有很多在研究如何对抗检测的,如何Bypass看这篇https://intoli.com/blog/not-possible-to-block-chrome-headless/,不过静态属性对抗检测貌似都是针对headless的检测,目前发现的在non-headless下的可检测项貌似只有window.navigator.webdriver === true
。
自己尝试去伪造了一下navigator.webdriver
:
|
|
|
|
|
|
|
|
启node服务:node server/index.js
,跑起来测试代码code runner - puppeteer.js
,然后发现non-headless chrome页面上显示true,控制台里输出是false,原因是Page.evaluate
方法是在页面加载之后执行的脚本,而对抗检测是在页面加载时,在puppeteer中篡改已经无用,篡改可以通过charles这样的代理工具完成js注入,在检测js前注入篡改的js。JS注入的检测,低级一点的注入之后在dom中留下了节点就容易检测,做的好的注入之后删除节点就基本没法检测了。
还有专门防御检测的插件puppeteer-extra-plugin-stealth,它的对抗衡量标准就是window静态属性检测;还可以通过reCaptcha score衡量,分数越接近1
越像用户行为,不过这个分数不能作为严格的衡量标准。这款插件待分析。看了一下这个插件借助的是puppetter的page.evaluateOnNewDocument api ,而这个api是对devTools protocol的Page.addScriptToEvaluateOnNewDocument 命令的包装。下面会提到chrome-remote-interface和puppeteer都是对devTools protocol的包装。
Puppeteer原理
想探究一下puppeteer.launch()
的背后,也就是想知道puppeteer是怎么和chromium交互的,找到了这个Using Chrome DevTools Protocol,然后大概看了下Puppeteer的源码,梳理了一下puppeteer的工作原理:
用spawn启了一个node子进程来跑chromium.app
,默认通过参数--remote-debugging-port
指定端口启动,这样会启一个Chrome DevTools Protocol server
同时返回一个WebSocket URL
,puppeteer会通过这个url
和DevTools host
建立一个websocket connection
,这样puppeteer就可以发出指令来操作chromium。(如果理解的没错,应该是这个逻辑,哈哈~)
下面整理了一下过程中发现的一些知识点:
通过
puppeteer.launch()
得到browser
,输出browser.wsEndPoint可以得到websocket url
,是类似这样的信息:1DevTools listening on ws://127.0.0.1:54497/devtools/browser/e3a9e795-b207-46ae-b9d8-51ee1b752716--remote-debugging-port=0
参数会随机启一个端口,想到之前在启前端服务时监听的ip如果设置了0.0.0.0
则会监听本机上的所有ip地址,通过任何一个ip地址都能访问到该服务,同理这里的端口0
已经不是一个真正意义上的端口而是一个集合。通过wsEndpoint
可以得到跑的端口,在浏览器中访问localhost:<port>
可以查看所有的Inspectable pages。建立websocket连接之后,看看发送了什么样的指令:
1234567{id: 1,method: 'Target.setDiscoverTargets',params: {discover: true}}每条指令都会有一个唯一的
id
,在websocket建立的通信通道中对发送指令的响应依据的就是id
。如果websocket响应内容没有id则是协议事件信息。所有的操作都有着对应的命令,也就是说,puppeteer提供出来的api是对遵循 Chrome DevTools Protocol协议的指令的包装。在puppeteer之前,想要控制headless chrome是通过chrome-remote-interface来实现,它是比 Puppeteer API 更接近底层的实现。
在关闭无头模式后可以看到浏览器上方有
Chrome is being controlled by automated test software.
的提示(关闭infobar的操作是添加启动参数--disable-infobars
,看这里)。
Puppeteer VS PhantomJS
至于puppeteer为什么会取代phantomjs,是因为puppeteer的爸爸是google?哈哈,开个玩笑。
phantomjs本身已经有一些问题,比如不能处理ES6规范,也不能处理一些新的移动端友好的css特性,跨平台兼容不友好等等。PhantomJS是基于webkit内核,Puppeteer则基于Blink分支内核。PhantomJS的maintainer也在2017年4月份宣布不再维护,找到了邮件原文:
Title: [Announcement] Stepping down as maintainer
Hi
I want to make an announcement.
Headless Chrome is coming - https://www.chromestatus.com/features/5678767817097216
(https://news.ycombinator.com/item?id=14101233)
I think people will switch to it, eventually. Chrome is faster and more stable than PhantomJS. And it doesn’t eat memory like crazy.
I don’t see any future in developing PhantomJS. Developing PhantomJS 2 and 2.5 as a single developer is a bloody hell.
Even with recently released 2.5 Beta version with new and shiny QtWebKit, I can’t physically support all 3 platforms at once (I even bought the Mac for that!). We have no support.
From now, I am stepping down as maintainer. If someone wants to continue - feel free to reach me.
I want to give credits to Ariya, James and Ivan! It was the pleasure to work with you. Cheers!
I also want to say thanks to all people who supported and tried to help us. Thank you!
With regards,
Vitaly.
有关的一些博文分析可以看这里:
Migration from PhantomJS to Chrome DevTools Protocol
自己之前也有用过PhantomJS,只记得貌似很容易被反爬拦截就结合使用了selenium,但是看到开源项目维护者发的这个邮件心里还是挺五味杂陈的,尤其看到这句I don't see any future in developing PhantomJS
时自己都有点伤感。但是看到那么多人用过、还在用PhantomJS,作为一个开源贡献者也一定是欣慰的。
期待自己也有能力去做开源贡献的那一天。
Ref
Blog: Chrome DevTools Protocol
IT IS NOT POSSIBLE TO DETECT AND BLOCK CHROME HEADLESS