什么是cdp
前天晚上想写个网站自动投稿,但是 chrome F12 抓的包里请求的几个参数里的值不知道 js 咋生成的,看不懂 js。询问了下网友,网友看我截图请求蛮多的,说有空帮我看看。并且他说到了模拟过程虽然能成功但是可能反爬措施强会导致封号,建议我用无头浏览器整。
搜了下相关概念,无头浏览器的话 python 里就是 selenium
驱动的,广泛使用的 headless browser 解决方案 PhantomJS 已经宣布不再继续维护,转而推荐使用 headless chrome。Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的 gui 前提下,使用所有 Chrome 支持的特性运行你的程序。
反爬措施的目的就是保证正常用户的访问,拒绝爬虫的访问。这个时候,我们就在思索一件事,不管他步骤怎样复杂化,他还是要对正常的浏览器提供业务支持,换而言之,他再复杂的请求步骤也会被浏览器完美执行。使用浏览器自己当爬虫,加大了资源消耗,爬取速度明显变慢,但是简化了开发步骤,缩短了开发周期,在某些情况下,这个技术还是非常有利可图的。
golang 里驱动headless chrome
有着开源库chromedp
(在2017年的gopher大会上有展示过),它是使用Chrome Debugging Protocol
(简称cdp) 并且没有外部依赖 (如Selenium, PhantomJS等)。
浏览器本身其实还充当着一个服务端的角色,大家应该都用过chrome浏览器的F12,也就是devtools,其实这是一个web应用,当你使用devtools的时候,而你看到的浏览器调试工具界面,其实只是一个前端应用,在这中间通信的,就是 cdp,他是基于 websocket 的,一个让 devtools 和浏览器内核交换数据的通道。cdp的官方文档地址 https://chromedevtools.github.io/devtools-protocol/ 可以点击查阅。
chromedp能做什么
- 反爬虫js,例如有的网页后台js自动发送心跳包,浏览器里会自动运行,不需要我们自动处理
- 针对于前端页面的自动化测试
- 解决类似VueJS和SPA之类的渲染
- 解决网页的懒加载
- 网页截图和pdf导出,而不需要额外的去学习其他的库实现
- seo训练和刷点击量
- 执行javascript 代码
- 设置dom的标签属性
使用前提
懂一点html和 css 以及js,因为操作 html 的 dom 元素需要用到 xpath 和 css 选择器之类的,如果 F12 的 element 里会右击复制 selector 也行,但是复杂的选择器还得需要 xpath 或者 css 选择器。不会使用的话简单教下:
chrome 打开网页 F12 后下面的调试工具出来后点击Elements
,然后点击elements右边的那个框框里的鼠标箭头,点击后变蓝色,然后放到网页上选中区域点击一下,下面的内容就跳到对应地方,然后下面右击html的标签->Copy
->COpy selector
或者xpath,就能复制选择器了。
安装
拉不下来的自行开GO111MODULE并且设置goproxy
1 | go get -u github.com/chromedp/chromedp@master |
场景一
- 打开必应页面
https://cn.bing.com/?mkt=zh-CN
- 输入
zhangguanzhang
- 点击搜索
- 打印第一个搜索结构的超链接地址
- 截图浏览器看到的界面
代码
1 | package main |
运行结果
1 | 2019/07/14 16:20:25 example: https://zhangguanzhang.github.io |
截图图片为:
Run函数接收一个context和Action接口的切片
1 | func Run(ctx context.Context, actions ...Action) error { |
godoc页面为 https://godoc.org/github.com/chromedp/chromedp
action不止Action
,还有QueryAction
,NavigateAction
,MouseAction
,KeyAction
…,自行查看godoc。其中的QueryAction
是依赖于元素定位去操作的,例如点击和文本框的输入,你得指定第一个参数传入xpath或者selector来筛选操作的标签去执行
1 | func XXXX(sel interface{}, opts ...QueryOption) QueryAction |
第二个参数是QueryOption,缺省是chromedp.BySearch
,允许使用CSS或XPath选择器查询元素,包装DOM.performSearch
常用选择器
1 | chromedp.BySearch // 如果不写,默认会使用这个选择器,类似devtools ctrl+f 搜索,效果等同于`document.querySelector(...)` 去测下 |
其他的自行去看go doc里讲解吧。下面说些其他的
调试和其他
讲解简单调和一些场景
UA
实际动手的时候发现一直hang住一样,才醒悟到网站应该检测了user agent了,下面代码借助网站返回ua
1 | package main |
输出
1 | 2019/07/14 17:21:09 user agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/75.0.3770.100 Safari/537.36 |
网站应该拦截了HeadlessChrome
,所以需要自行设置ua
这是包里默认的flag数组,记住是数组
1 | var DefaultExecAllocatorOptions = [...]ExecAllocatorOption{ |
还有一些可能需要用到的
- –no-first-run 第一次不运行
- –default-browser-check 不检查默认浏览器
- –headless 不开启图像界面
- –disable-gpu 关闭gpu,服务器一般没有显卡
- –remote-debugging-port chrome-debug工具的端口(golang chromepd 默认端口是9222,建议不要修改)
- –no-sandbox 不开启沙盒模式可以减少对服务器的资源消耗,但是服务器安全性降低,配和参数 - –remote-debugging-address=127.0.0.1 一起使用
- –disable-plugins 关闭chrome插件
- –remote-debugging-address 远程调试地址 0.0.0.0 可以外网调用但是安全性低,建议使用默认值 127.0.0.1
- –window-size 窗口尺寸
更多参数说明详解headless-chrome官方文档 https://developers.google.com/web/updates/2017/04/headless-chrome
1 | package main |
输出
1 | 2019/07/14 17:24:49 user agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 |
开启GUI来debug
然后还是遇到了hang住,不知道为啥,询问了别人说可以关闭headless来开启gui,这样可以看到chrome具体在干啥了
虽然默认选项里是开启了headless,但是我们可以利用切片在尾部追加,来覆盖掉前面的选项,例如
1 | $ seq 5 | head -n 1 |
而headless的函数内容为
1 | func Headless(a *ExecAllocator) { |
所以开启gui这样写
1 | package main |
运行会看到chrome被打开一个新窗口,写着被自动控制着,如果遇到问题我们可以实时的观察
设置chrome的execPath
实际上运行都是依赖于机器上有chrome浏览器,这是包里的代码
1 | func ExecPath(path string) ExecAllocatorOption { |
如果我们的安装路径变了可以用ExecPath设置下
官方的demo
执行js
有些函数不会返回,所以可以下面,或者[]byte
1 | chromedp.Evaluate(`document.getElementById('iframe').contentWindow.document.querySelector('div[class^=ckplayer] video').click();aaa="111"`, |
https://github.com/chromedp/chromedp/issues/381#issuecomment-500662646
一些坑
iframe
https://github.com/chromedp/chromedp/issues/72#issuecomment-642151827
https://github.com/chromedp/chromedp/blob/49daeb65bff2056e97c1eaee79c8e924566ae675/nav_test.go#L278-L286
https://github.com/chromedp/chromedp/issues/212#issuecomment-416462733