分享一个用于自动化和爬虫的库

项目地址: https://github.com/go-rod/rod

感谢大家的反馈优化了不少 API,因为之前的帖子已经过时了,所以在这里重新发下帖子。近期主要优化了异常处理。基本覆盖了 puppeteer 的主要功能,且支持更多便利的高级功能,比如等待 ajax,复杂代理,文件下载等。

这个库的主要原理就是利用浏览器的 devtools 可编程接口来操控浏览器。日常你可以用它自动化一些重复的页面任务,相比油猴子有更多的高级功能和 golang 强大的生态。工作上可以用它辅助集成化测试减少人力消耗。开发上可以用它简化一些传统爬虫需要费很多时间逆向工程的场景。

欢迎对自动化有兴趣的朋友加入到项目中来,这是我们的聊天室: https://discord.gg/PAaMGJw

可以通过示例文件大致了解 rod 的一些基本用法: https://github.com/go-rod/rod/blob/master/examples_test.go

下图是 rod,puppeteer 和 chromedp 实现相同输出的代码对比:

rod vs puppeteer vs chromedp

更多对比请移步: https://github.com/go-rod/rod/tree/master/lib/examples/compare-chromedp

关于 Must 前缀: https://github.com/go-rod/rod/blob/03b1d54362f72599d3bfa465aee6b48c9c51599b/examples_test.go#L137-L186

相关文章

48 thoughts on “分享一个用于自动化和爬虫的库

  1. @supermoonie 好奇你这个问题的出发点是啥。要在单核 CPU 上跑吗?理论上用了 Go 一个线程就够了 (green threads),不是很清楚该如何回答你这个问题。

  2. @playniuniu 确实,目前还没有教程,感觉最近可能会花点时间出点教程,比如录个视频或者直播之类的会对我来说负担小点,写文章比较费精力。确实是想收集大家的想法才发帖子的,有什么具体想学的吗?

  3. 如果有可能 能否找几个网站 从最简单的 解析网页表单 ,操作鼠标点击 到比较复杂的过滑动验证码 eval js 等等 还有比较骚操作的 hijack 请求 bypass 这些 每一个能给个案例并讲解一下 rod 实现原理就好了

  4. @playniuniu 好的,应该问题不大。还会讲解下如何利用 rod 调试复杂场景,这个也是很多人常常问的,这种就很难写文章,不录屏幕太麻烦解释了。

  5. 是我火星了还是 promise 不支持 timeout 了? Pupeteer 不支持 abort slow operation,这个不是用 promise timeout 封装一下就行了么?

  6. @xwhxbg 我是自己实现过 promise 库的,研究了很多 nodejs 底层工作原理的: https://github.com/ysmood/yaku

    这说法其实是对比于 Go 的 context 特性来说的。大致就是指,比如一个 operation 要耗费一个小时,如果你用 nodejs 的 promise,实际上这个 operation 相关的资源还是驻留在内存的,并没有 abort 释放,只有到一个小时后才会释放。比如 cdp 的通信,由于是基于 websocket 的 frame,puppeteer 并没有实现原子级别的 abort 。你要想释放单个请求只能断开整个 websocket 链接,而 rod 这种就不用了,可以很原子的操作细节。

  7. @ysmood 呃,大家不都是用 bluebird 家的 promise 实现么? ES6 的确实没法 cancel,但是 bluebird 家的有 abort 之类的

  8. @xwhxbg 所以你得去了解 abort 的底层机制,bluebird 也没法解决我说的问题,它的 abort 对我来说都是虚假的 abort,实际还是在后台消耗资源。

  9. @xwhxbg golang 贯穿整个系统底层的 context 概念是 nodejs 是很难比拟的: https://golang.org/pkg/context/

    Promise 设计之初压根就没好好考虑 abort,草案直接放弃了这个方向的探寻。

    写 puppeteer 大量重复的 async await 反复横跳于代码间。有时候写漏了 await 还不容易察觉。我个人觉得严重破坏了用 IDE 自动点点点补全的流畅感。

  10. @ysmood 我实在是没看懂你说的“底层机制”是什么,如果想要 cancel 请求,es6 就有 abort,如果想要 then 里面直接跳过,立刻回收内存,bluebird 也能实现

  11. @ysmood context 只是一个 handle 而已啊,贯穿底层是指什么呢? promise 传一个 object 当 handle 也是可以的啊。

    golang 的 cancel 就是很正常的一个 handle,文档也说了`A CancelFunc does not wait for the work to stop. `说明即使调用了 cancel 也不能让代码立刻结束

    可以描述一下你想要的 cancel 是什么效果吗?

  12. @xwhxbg 这个 google 一下类似关键字都可以出结果: https://stackoverflow.com/questions/29478751/cancel-a-vanilla-ecmascript-6-promise-chain

    你看这个人说 bluebird 可以,然后评论的人就说的很清楚了:

    this definition of canceled is just rejecting the promise. it depends on the definition of “canceled”

    This is partially right, but if you have long promise chain, this approach would not work.

    我觉挺清楚了啊,各种人都讨论过这个问题。

    你说 ”es6 就有 abort“,能给个 ES6 官方的 spec 链接 吗?我好奇啥时候出的

  13. @ysmood 用 java 实现过这个协议,因为没有用异步 io,websocket 发送一个线程,接收一个线程,事件处理也是一个线程池在支持,所以问下 go 实现的话,是不是像 node 那样基于事件处理?

  14. @xwhxbg abort 是不可能由外部独立完成的,必然是要 task 自己轮询是否有中断机信号,这个是计算机原理的基础。promise 是 task 自己返回一个承诺,而 golang context 是向 task 传递中断信号。这个根本差异很难用一些三方库抹平,必须要改很多库的接口方式。puppeteer 是没有实现这种细粒度中断信号控制的。所以我觉得这里我表达的应该还算合理。

  15. @xwhxbg 所以问题的关键是 puppeteer 没给你接口让你传这个 cancel 的信号进去啊,这样说你觉得还有问题吗?

  16. @ysmood 你的意思是 golang 可以做到行级别的 cancel,能在一个 function 调用一半的时候不执行下半部分么?

  17. @supermoonie 用的 goroutine 不是 libuv 那种。基于 go channel 的通信,不是 epoll,各有优劣。不会像 node 那样要各种侦听事件,写法基本是线性的,不会出现一大堆 callback

  18. @xwhxbg 我刚不是说了计算机原理了吗? 当然是不可以的,这不是要对比 golang 和 nodejs,这里只是比较 rod 和 puppeteer 在处理 io block 的 cancel 上。rod 确实能更细力度的处理 cancel 信号,任何可以 io block 的地方基本都可以 cancel

  19. @xwhxbg 比如 puppeteer 在模拟 click 时,会发送多个请求到 chrome,你没法阻止这几个请求只发出去一半。rod 可以。

  20. @ysmood 那我换种问法,一个 Tab 需要多少个 goroutine 支持?我可能比较关注性能这块,因为 Chrome 本来就比较消耗资源

  21. @ysmood 今天下班了我去看看你的源码,没有异步 IO 竟然可以做到只有几个 goroutine,很好奇是怎么做到的

  22. @yejianmail java 有 cdp4j,现在好像有社区版,不过代码写的不是太好,线程管理都没有

  23. @ysmood review 不了,毕竟不是搞 go 的,准备抽时间,用 java nio 的封装库 netty 写一遍,之前用的阻塞 io,不是很满意

  24. @supermoonie 写通信异步处理这块算是这个库最小儿科的部分了。核心代码只有 200 行 https://github.com/go-rod/rod/blob/03b1d54/lib/cdp/client.go#L118

    这个项目真正难的是如何在没有详细文档和资料的情况下用 cdp 接口完成一些高级功能,chrome 官方的文档非常糟糕,而且 cdp 接口设计本身也问题很大。比如单是模拟完美的 click 都够你折腾一阵子,看似简单的 click 需要调用约十个接口完成,chrome 只列举了全部接口,但是要调用哪 10 个接口完成像人一样点击鼠标是没有说的。

    你要是感兴趣,go 挺简单的,比起你去研究 puppeteer,把这个项目翻译成 java 估计会更简单。

    其实更好的做法不是用各种语言实现一遍相同的功能,而是基于 rod 这种库开发语言中性的服务,我们有个 issue 你可以看看: https://github.com/go-rod/rod/issues/21

    我们也有个语言中性化的试验性项目 https://github.com/go-rod/wayang

  25. @ysmood emmm 。。。异步通信对于 go 可能简单,但是对于 Java 来说却不是小儿科,而且我觉得

    https://chromedevtools.github.io/devtools-protocol/

    官方文档写的挺详细了,你是不是没有找对地方?其实两年前我就已经把 devtools-protocol 实现了

    https://github.com/supermoonie/AutoChrome

    只不过我觉得有些地方做的不够好,就搁置了,最近 Netty ( Java 异步 IO 库)学得差不多了,就想再用异步 IO 实现下。

    其实 go 这方面做的挺好的,只是实在个人精力有限(个人项目在开发中。。。),要不然我也去搞搞 go 了,原本我还想用 Qt 去写一个 QAutoChrome 以及 Python 写 PyAutoChrome

    https://github.com/supermoonie/PyAutoChrome
    https://github.com/supermoonie/QAutoChrome

    只不过没时间罢了,哈哈

  26. @supermoonie 就是个经典生产消费模型而已,了解了原理,让我用 java 写我也觉得挺简单的,就算不用 netty 也可以的,瓶颈不在这儿。我觉得没必要争论这个,个人主观感受而已,仁者见仁智者见智。

    https://chromedevtools.github.io/devtools-protocol/ 这个我是知道的,rod 文档里也多次提到了这个,而且 rod 本身很多代码也是自动生成于这个的。

    我认识的深入开发过 cdp 的人都觉得 https://chromedevtools.github.io/devtools-protocol/ 写的不够详细,比如 Page.createIsolatedWorld 的文档,我是看不懂的,比如 IsolatedWorld 和 v8 的关系,生命周期,作用范围啥都没说。一堆类似的不明不白的 API 说明。

  27. @ysmood 哈哈哈,这倒是,不过这些 api 无关紧要。突然想起来不打算搞 cdp 的一个重要原因,cef …… 前段时间已经打通了 cef 的 java 绑定,各种 api 随便调用,还实现了一个简单的浏览器,然后就觉得 cdp 不香了

  28. @supermoonie cef 跟 cdp 要解决的问题完全不同。而且我要用 cef 的话,还用个毛的 java,直接 c++ 了,何必折腾自己呢。学个语言的成本比跨语言 embed 坑要少多了。

    比如我可以立刻用同时用 puppeteer 和 rod 控制几千个 chromium 的集群,你用 cef 不又是要自己来一套 cdp 的轮子才能做到这种级别的解耦吗?

  29. @iiusky 截图都是我实际跑过结果的,底下的链接你可以访问然后自己跑。能说下 pjs 是什么意思吗?没看懂

发表评论

电子邮件地址不会被公开。 必填项已用*标注