chromedp网络监听_动态爬虫三:监听网络事件 + 监听js事件
发布日期:2021-06-24 17:13:27 浏览次数:2 分类:技术文章

本文共 7535 字,大约阅读时间需要 25 分钟。

一: 概述


       上两篇文章介绍了cdp协议和chromedp库,从这篇文章开始动手实战一下,我们要拿到页面上更多的网络请求,最直接的想法就是类似于开发者工具里的network,只有一有网络请求就显示在列表里(在network里隐藏的网络请求后续讨论),完后页面加载完后就可以浏览页面操作页面了,比如 点击,滑动等等,这些都是 js 的事件,如果能把所有绑定了事件的元素都找到, 再去触发它,那么网络请求那边就能监听到。

二:小试牛刀


这是开发者工具的网络窗口:

c476486f0e47e13877c46d9b04b848c8.png

思路有了接下来我们就是要找到一个合适的事件,打开协议监控(参考上几篇文章),完后刷新页面,完后搜索 network 就会看到和网络相关的事件了(network是来源于cdp协议文档里)

0a13ad813581bf3f4ad4ca999ad12ba4.png

从这个字面理解网络请求即将发送,很适合啊,就它了。

291f6552b540235fac790cf684b2ca4b.png

顺便看一下其他事件,下面这个很适合采集响应

4f216bbbe9342f7500c05d444aeb7d4f.png

使用之前搭建的框架试一下,代码结构如下:

9f83111f535a43cfc85bc2605932dc82.png

项目结构等完后单独写篇文章讨论,上面提到的逻辑都写在 scan.go 中了,总体代码如下:

func run(cmd *cobra.Command, args []string) {
sugar := logger.Sugar() var ws sync.WaitGroup listRequest := list.New() listResponse := list.New() options := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.Flag("headless", true), chromedp.Flag("disable-gpu", false), chromedp.Flag("disable-extensions", true), chromedp.Flag("hide-scrollbars", false), chromedp.Flag("mute-audio", false), chromedp.Flag("enable-automation", false), chromedp.UserAgent(`Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36`), ) defaultCtx, cancel := chromedp.NewExecAllocator(context.Background(), options...) defer cancel() withLogCtx, cancel := chromedp.NewContext(defaultCtx, chromedp.WithLogf(log.Printf)) defer cancel() timeoutCtx, cancel := context.WithTimeout(withLogCtx, time.Duration(viper.GetInt64("timeout")) *time.Second) defer cancel() if err := chromedp.Run(timeoutCtx, make([]chromedp.Action, 0, 1)...); err != nil {
sugar.Fatal("create chrome error", err) } sugar.Info("Chrome instance create success. \n") chromedp.ListenTarget(timeoutCtx, func(event interface{}) {
switch event.(type) {
case *network.EventRequestWillBeSent: ws.Add(1) go func(req *network.EventRequestWillBeSent) {
harReq := common.ProcessRequest(req) rm := map[network.RequestID]*har.Request{} rm[req.RequestID] = harReq listRequest.PushBack(rm) ws.Done() }(event.(*network.EventRequestWillBeSent)) case *network.EventResponseReceived: ws.Add(1) go func(resp *network.EventResponseReceived) {
harResp := common.ProcessResponse(resp) rm := map[network.RequestID]*har.Response{} rm[resp.RequestID] = harResp listResponse.PushBack(rm) ws.Done() }(event.(*network.EventResponseReceived)) case *network.EventDataReceived: // TODO:: } }) if err := chromedp.Run(timeoutCtx, network.Enable()); err != nil {
panic(err) } if err:= chromedp.Run(timeoutCtx, dom.Enable()); err != nil {
panic(err) } if err:= chromedp.Run(timeoutCtx, chromedp.Navigate(args[0])); err != nil {
panic(err) } if err := chromedp.Run(timeoutCtx, common.WalkAllNode()); err != nil {
panic(err) } ws.Wait()}

创建浏览器实例部分之前解释过,这里就不多说了,我们现在创建了一个浏览器实例,得为我们的浏览器设置网络监听处理,就是下面这段

chromedp.ListenTarget(timeoutCtx, func(event interface{}) {
switch event.(type) {
case *network.EventRequestWillBeSent: 发请求前监听的事件 ws.Add(1) go func(req *network.EventRequestWillBeSent) {
harReq := common.ProcessRequest(req) rm := map[network.RequestID]*har.Request{} rm[req.RequestID] = harReq listRequest.PushBack(rm) ws.Done() }(event.(*network.EventRequestWillBeSent)) case *network.EventResponseReceived: 收到响应监听的事件 ws.Add(1) go func(resp *network.EventResponseReceived) {
harResp := common.ProcessResponse(resp) rm := map[network.RequestID]*har.Response{} rm[resp.RequestID] = harResp listResponse.PushBack(rm) ws.Done() }(event.(*network.EventResponseReceived)) case *network.EventDataReceived: // TODO:: } })

这些事件最好都在协程里处理, 同时注意要开启网络监听,我弄那会儿就折腾了半天抓不到流量,仔细看文档才发现的

if err := chromedp.Run(timeoutCtx, network.Enable()); err != nil {
panic(err)}

中间插一个介绍,har 这个东西,我之前都不知道,翻源码发现的,就是专门记录http流量的一个格式

742a19fc1c8dc70d13e7570f1237570d.pngd2ebb37124a1020dd99529d25260860a.png

维基百科介绍: https://zh.wikipedia.org/wiki/.har

反正现在还不知道存成啥样子呢,先按这个格式来吧,上面写了2个处理函数,代码如下:

func ProcessRequest(r *network.EventRequestWillBeSent) *har.Request {
req := har.Request{} req.Method = r.Request.Method req.URL = r.Request.URL req.Headers = []*har.NameValuePair{} for header := range r.Request.Headers {
h := har.NameValuePair{} h.Name = header h.Value = r.Request.Headers[header].(string) req.Headers = append(req.Headers, &h) } req.Cookies = []*har.Cookie{} req.QueryString = []*har.NameValuePair{} u, err := url.Parse(req.URL) if err != nil {
log.Printf("[E] Invalid URL data recived : %v", err) } for name := range u.Query() {
if len(name) != 0 {
values := u.Query()[name] for _, val := range values {
req.QueryString = append(req.QueryString, &har.NameValuePair{
Name: name, Value: val, }) } } } if req.Method == "POST" {
// Process the post data of the form *har.PostData } return &req}func ProcessResponse(r *network.EventResponseReceived) *har.Response {
res := har.Response{} res.Status = r.Response.Status res.StatusText = r.Response.StatusText res.HTTPVersion = r.Response.Protocol // TODO : implement cookie information. res.Cookies = nil res.Headers = []*har.NameValuePair{} // headers from the *network.EventRequestWillBeSent are in the form, // map[key:value]. this needs to be converted to the form of a // har.NameValuePair for header := range r.Response.Headers {
h := har.NameValuePair{} h.Name = header h.Value = r.Response.Headers[header].(string) res.Headers = append(res.Headers, &h) } // response content res.Content = &har.Content{} res.Content.MimeType = r.Response.MimeType res.Content.Size = 0 // Redirect URL res.RedirectURL = "" res.HeadersSize = 0 res.BodySize = 0 return &res}

好了,跑一下程序试试效果

e97399ff9dfcb45ca315315176d187e4.png

看着还可以,哈哈哈,接下来该到查看所有带事件的节点了,我们知道 html 最后会解析为一棵 DOM 树,那么怎么获取这个树呢,还是翻文档,发现个这个,如下:

c36b0827b53e6b5b58a884d2b07a300c.png

把所有节点打平到一个数组里,正是我们想要的。数组里是 Node 结构,如下:

ad63607c741400e909e330966d15769f.png

翻了一下发现好像没事件相关的东西,这个地方折腾了好一会儿,还是看文档,如下:

481841b2dc9e56c1aae80a71c5aeed64.png

事件相关的在 DOMDebugger 中,这个方法可以获取所有事件监听。但是这个函数要的参数是一个 RemoteObjectID。

517236954a87173009911214c64a0e8b.png

我擦,此时陷入了迷茫,我有的只有一个 nodeID,怎么获取 RemoteObjectId ???

d36f36820d160f036d8d38f32c7092e1.png

功夫不负有心人,找到一篇文章

67d58c058ccd45c99ae1fbfa0b2750ce.png

(链接:https://stackoverflow.com/questions/31280653/what-is-nodeid-in-chrome-extension-chrome-debugger)

搞清楚后,还是回到文档,开始找找转换函数,于是找到了这个货

f59fe6b082b30fb79398d879b556887c.png

胜利就在前方,上代码

func WalkAllNode() chromedp.Tasks {
return chromedp.Tasks{
chromedp.ActionFunc(func(ctx context.Context) error {
node, err := dom.GetFlattenedDocument().Do(ctx) if err != nil {
return err } for _, tmpNode := range node {
rmObject, _ := dom.ResolveNode().WithNodeID(tmpNode.NodeID).Do(ctx) eventLister, _ := domdebugger.GetEventListeners(rmObject.ObjectID).Do(ctx) if len(eventLister) > 0 {
fmt.Println(tmpNode.NodeName, tmpNode.Attributes) for _, event := range eventLister {
fmt.Println(event.Type) } } } return nil }), }}

调用的地方在这里

...if err:= chromedp.Run(timeoutCtx, chromedp.Navigate(args[0])); err != nil {
panic(err)}if err := chromedp.Run(timeoutCtx, common.WalkAllNode()); err != nil {
panic(err)}ws.Wait()...

跑一下,效果如下

609cfcc2427a71405860167e12cb2024.png

和开发者工具里的对比一下

b7258e725902693b47741f5e02c6c8e4.png

顺利拿到,下一篇分享怎么触发这些事件。

三:总结


        感觉文档真的很重要,之前没接触过这些,上面只是最普通的情况,好多其他的情况都没考虑,慢慢来,前端博大精深。 

转载地址:https://blog.csdn.net/weixin_34026997/article/details/112077398 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:java并发编程_Java并发编程之 synchronized
下一篇:linux 普通用户touch权限不够_一篇文章让你轻松了解 Linux 的权限

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2024年04月14日 17时22分39秒