• cdp协议简介


    啥是cdp

    根据官网的说法,cdp(Chrome DevTools Protocol) 允许我们检测,调试Chromium, Chrome 和其他基于 Blink的 浏览器. 这个协议被广泛使用. 其中最著名的是 Chrome DevTools,协议的api也由这个团队维护。

    使用cdp的姿势

    首先需要打开 "C:Program Files (x86)GoogleChromeApplicationchrome.exe"    --remote-debugging-port=9222

    如果在浏览器中,当你打开devtools时,其实你已经在使用cdp了,只是感知不深罢了,一种办法可以更直观的感知cdp,就是打开devtools的devtools,具体操作如下:

    1. 将开发者工具设置为独立窗口,dock side点第一个
    2. 在开发者工具上再使用快捷键ctrl+shift+i,就可以打开开发者工具的开发者工具了(就是先打开开发者工具成独立窗口;再在这个独立窗口上再用快捷键ctrl+shift+i,又打开了开发者工具),现在在新打开的开发者工具的console里面,输入下面的代码:
    let Main = await import('./main/main.js');
    Main.MainImpl.sendOverProtocol('Runtime.evaluate', {expression: 'alert (12345)'});
    

    这时网页会alert 12345,你会发现平时在控制台简单的代码执行,其实是通过cdp远程调用网页的js引擎去执行再返回结果的。

    除此之外,protocol monitor也可以帮助我们更直观的理解cdp。

    几个重要的URL

    当一个页面暴露出它的remote debugging port时,我们就可以借助cdp来对这个网页进行remote debugging了。由于cdp是借助websocket实现的,所以,在一切开始之前,有两个url是比较重要的
    http://localhost:[port]/json/list
    http://localhost:[port]/json/version
    这两个url,可以让我们拿到网页的websocket url,json/list返回的数据类似于:

    [
    {
    description: "",
    devtoolsFrontendUrl: "/devtools/inspector.html?ws=localhost:8080/devtools/page/a31c4d5c-b0df-48e8-8dcc-7c98964e2ebe",
    id: "a31c4d5c-b0df-48e8-8dcc-7c98964e2ebe",
    title: "",
    type: "page",
    url: "xxx://xxx",
    webSocketDebuggerUrl: "ws://localhost:8080/devtools/page/a31c4d5c-b0df-48e8-8dcc-7c98964e2ebe"
    }
    ]
    

    其中webSocketDebuggerUrl就是我们需要的打开remote debugging 的钥匙

    重头戏websocket

    接下来我们连上ws,就可以愉快的远程操作页面了,正如chrome devtools所做的那样,下面是一个例子:

    const WebSocket = require('ws');
    const puppeteer = require('puppeteer');
    
    (async () => {
      // Puppeteer launches browser with a --remote-debugging-port=0 flag,
      // parses Remote Debugging URL from Chromium's STDOUT and exposes
      // it as |browser.wsEndpoint()|.
      const browser = await puppeteer.launch();
    
      // Create a websocket to issue CDP commands.
      const ws = new WebSocket(browser.wsEndpoint(), {perMessageDeflate: false});
      await new Promise(resolve => ws.once('open', resolve));
      console.log('connected!');
    
      ws.on('message', msg => console.log(msg));
    
      console.log('Sending Target.setDiscoverTargets');
      ws.send(JSON.stringify({
        id: 1,
        method: 'Target.setDiscoverTargets',
        params: {
          discover: true
        },
      }));
    })();
    

    更多例子可以在这里

    jsonRPC

    如上面例子所示,当ws连接后,一个发给浏览器的指令大概包括3部分id,method,params,比如一个执行一段console.log('hello')代码的指令:

    {
      "id": 235,
      "method": "Runtime.evaluate",
      "params": {
        "expression": "console.log('hello');",
        "objectGroup": "console",
        "includeCommandLineAPI": true,
        "silent": false,
        "contextId": 1,
        "returnByValue": false,
        "generatePreview": true,
        "userGesture": true,
        "awaitPromise": false
      }
    }
    
    

    chrome devtools可以完成的功能非常庞大,而这些功能基本都是使用这样的一个个指令实现的,让人想起那句古老的中国名言:九层之台,起于垒土。本文完

    参考资料:
    https://chromedevtools.github.io/devtools-protocol
    https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md

    文章来源: www.cnblogs.com,作者:nobody-junior,版权归原作者所有,如需转载,请联系作者。

    原文链接:https://www.cnblogs.com/imgss/p/12852595.html


    这边我选择的是 python 的 pychromegithub 地址,使用方法很简单,直接看 github 上它的 Demo

    这个库依赖websocket-client

    获取 performance api 数据

    这里使用 Runtime Domain 中运行 JavaScript 脚本的 APIRuntime.evaluate

    # 开始前先启动chrome,启动chrome必须带上参数`--remote-debugging-port=9222`开启远程调试否则无法与chrome交互
    browser = pychrome.Browser('http://127.0.0.1:%d' % 9222)
    tab = browser.new_tab()
    tab.start()
    tab.Runtime.enable()
    tab.Page.navigate(url={你的页面地址})
    # 设置等待页面加载完成的时间
    tab.wait(10)
    # 运行js脚本
    timing_remote_object = tab.Runtime.evaluate(
                expression='performance.timing'
            )
    # 获取performance.timing结果数据
    timing_properties = tab.Runtime.getProperties(
                objectId=timing_remote_object.get('result').get('objectId')
            )
    timing = {}
    for item in timing_properties.get('result'):
                if item.get('value', {}).get('type') == 'number':
                        timing[item.get('name')] = item.get('value').get('value')
    # 获取performance.getEntries()数据
    entries_remote_object = tab.Runtime.evaluate(
                expression='performance.getEntries()'
            )
    entries_properties = tab.Runtime.getProperties(
                objectId=entries_remote_object.get('result').get('objectId')
            )
    entries_values = []
    for item in entries_properties.get('result'):
      if item.get('name').isdigit():
        url_timing_properties = tab.Runtime.getProperties(
                        objectId=item.get('value').get('objectId')
                    )
         entries_value = {}
         for son_item in url_timing_properties.get('result'):
                        if (son_item.get('value', {}).get('type') == 'number'or
                                son_item.get('value', {}).get('type') == 'string'):
                            entries_value[son_item.get('name')] = son_item.get('value').get('value')
                    entries_values.append(entries_value)
    
    

    获取 Network 数据

    实际上 performance.getEntries() 不会记录 404 的请求信息,另外当前页面通过 js 触发新 html 页面请求时它只会记录第一个页面的请求,在这些情况下就需要通过 Network Domain 的 API 来收集所有请求信息,先介绍用到的 API:

    1. Network.requestWillBeSent每个 http 请求发送前回调
    2. Network.responseReceived首次接送到 http 响应时回调
    3. Network.loadingFinished请求加载完成时回调
    4. Network.loadingFailed请求加载失败时回调

      # 封装上面4个事件对应的回调方法
      class NetworkAPIImplemention(object):
      
      def __init__(self):
          self.request_dict = {}
          # 首个请求开始时间
          self.start = None
      
      def request_will_be_sent(self, **kwargs):
          if self.start is None:
              self.start = time.time()
          dict_http = {
              'url':kwargs.get('request').get('url'),
              'start':kwargs.get('timestamp')
          }
          self.request_dict[kwargs.get('requestId')]=dict_http
          #print "loading:%s" % kwargs.get('request').get('url')
      
      def loading_finished(self, **kwargs):
          # 服务器返回code 例如404也是finished
          self.request_dict[kwargs.get('requestId')]['end'] = kwargs.get('timestamp')
          self.request_dict[kwargs.get('requestId')]['size'] = kwargs.get('encodedDataLength')
      
      def response_received(self, **kwargs):
          self.request_dict[kwargs.get('requestId')]['type'] = kwargs.get('type')
          self.request_dict[kwargs.get('requestId')]['response'] = kwargs.get('response')
      
      def loading_failed(self, **kwargs):
          self.request_dict[kwargs.get('requestId')]['end'] = kwargs.get('timestamp')
          self.request_dict[kwargs.get('requestId')]['error_text'] = kwargs.get('errorText')
      network_api = NetworkAPIImplemention()
      browser = pychrome.Browser('http://127.0.0.1:%d' % 9222)
      tab = browser.new_tab()
      # 绑定回调函数
      tab.Network.requestWillBeSent = network_api.request_will_be_sent
      tab.Network.responseReceived = network_api.response_received
      tab.Network.loadingFinished = network_api.loading_finished
      tab.Network.loadingFailed = network_api.loading_failed
      tab.start()
      tab.Network.enable()
      tab.Runtime.enable()
      # 是否禁用缓存
      if disable_cache:
      tab.Network.setCacheDisabled(cacheDisabled=True)
      tab.Page.navigate(url={你的页面地址})
      tab.wait(10)
      tab.stop()
      self.browser.close_tab(tab)
      # 获取的所有url详细信息
      print network_api.request_dict
      

    监听页面事件

    有时候特别是一些复杂的页面,页面依赖 js 和后端资源数据,并不是通常意义上页面 loadEventEnd 事件触发完就表示页面加载完成,这种情况可能需要依赖开发打点。
    这里以开发设计了一个Loaded事件为例

    # 具体事件注册方式和注册时机询问开发,所谓注册时机即要求在js对象生成后注册,我们项目中page是在一个js文件中声明的,需要等这个js文件请求完成后再注册
    # 这边使用Promise方式,这种方式awaitPromise参数必须是True
    js = """
        new Promise((resolve, reject) => {
            page.getController().getPageEvent().addEventListener("Loaded",
                    function(){
                        resolve(new Date().getTime());
                    });
            });
       """
    custom_result = tab.Runtime.evaluate(
        expression=js,
        awaitPromise=True,
        timeout=timeout * 1000
    )
    print custom_result.get('result').get('value')
    

    有个坑peformance.now()获取与 chrome 开发者工具协议一样类型的时间时,这个时间不准确,只好用new Date().getTime()

    写在最后

    一开始是使用 nodejs 的 chrome-remote-interface,但是发现Page.loadEventFired回调后不会再记录请求,事实上有些页面仍然有请求没有完成,不懂是不是我使用姿势不对 
    附赠 W3C 的一幅图

  • 相关阅读:
    多线程
    Java命令行传参
    IO流
    集合
    Java基础语法
    常见的数据结构
    泛型
    java 集合重要概念 (Set 的存储内存解析)
    java 集合重要概念 (== 和 equals 解读)
    java 集合重要概念 (实现一个简单的斗地主)
  • 原文地址:https://www.cnblogs.com/bigben0123/p/14591303.html
Copyright © 2020-2023  润新知