cdp协议简介
啥是cdp
根据官⽹的说法,cdp(Chrome DevTools Protocol) 允许我们检测,调试Chromium, Chrome 和其他基于 Blink的浏览器. 这个协议被⼴泛使⽤. 其中最著名的是 Chrome DevTools,协议的api也由这个团队维护。
使⽤cdp的姿势
⾸先需要打开: "C:\Program Files (x86)\Google\Chrome\"    --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
[
{
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
chrome浏览器是啥浏览器接下来我们连上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!');
<('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可以完成的功能⾮常庞⼤,⽽这些功能基本都是使⽤这样的⼀个个指令实现的,让⼈想起那句古⽼的中国名⾔:九层之台,起于垒⼟。本⽂完
⽂章来源: wwwblogs,作者:nobody-junior,版权归原作者所有,如需转载,请联系作者。
这边我选择的是 python 的 pychrome,使⽤⽅法很简单,直接看 github 上它的 Demo
这个库依赖
获取 performance api 数据
这⾥使⽤ Runtime Domain 中运⾏ JavaScript 脚本的 API Runtime.evaluate
# 开始前先启动chrome,启动chrome必须带上参数`--remote-debugging-port=9222`开启远程调试否则⽆法与chrome交互
browser = pychrome.Browser('127.0.0.1:%d' % 9222)
tab = w_tab()
tab.start()
able()
tab.Page.navigate(url={你的页⾯地址})
# 设置等待页⾯加载完成的时间
tab.wait(10)
# 运⾏js脚本
timing_remote_object = tab.Runtime.evaluate(
expression='performance.timing'
)
# 获取performance.timing结果数据
timing_properties = Properties(
objectId=timing_('result').get('objectId')
)
timing = {}
for item in ('result'):
('value', {}).get('type') == 'number':
('name')] = ('value').get('value')
# 获取Entries()数据
entries_remote_object = tab.Runtime.evaluate(
expression='Entries()'
)
entries_properties = Properties(
objectId=entries_('result').get('objectId')
)
entries_values = []
for item in ('result'):
('name').isdigit():
url_timing_properties = Properties(
('value').get('objectId')
)
entries_value = {}
for son_item in url_('result'):
if (('value', {}).get('type') == 'number'or
('value', {}).get('type') == 'string'):
entries_value[('name')] = ('value').get('value')
entries_values.append(entries_value)
获取 Network 数据
实际上 Entries() 不会记录 404 的请求信息,另外当前页⾯通过 js 触发新 html 页⾯请求时它只会记录第⼀个页⾯的请求,在这些情况下就需要通过 Network Domain 的 API 来收集所有请求信息,先介绍⽤到的 API:
1. questWillBeSent每个 http 请求发送前回调
2. sponseReceived⾸次接送到 http 响应时回调
3. Network.loadingFinished请求加载完成时回调
4. Network.loadingFailed请求加载失败时回调
# 封装上⾯4个事件对应的回调⽅法
class NetworkAPIImplemention(object):
def __init__(self):
# ⾸个请求开始时间
self.start = None
def request_will_be_sent(self, **kwargs):
if self.start is None:
self.start = time.time()
dict_http = {
'url':('request').get('url'),
'start':('timestamp')
}
#print "loading:%s" % ('request').get('url')
def loading_finished(self, **kwargs):
# 服务器返回code 例如404也是finished
def response_received(self, **kwargs):
def loading_failed(self, **kwargs):
network_api = NetworkAPIImplemention()
browser = pychrome.Browser('127.0.0.1:%d' % 9222)
tab = w_tab()
# 绑定回调函数
questWillBeSent = quest_will_be_sent
sponseReceived = sponse_received
tab.Network.loadingFinished = network_api.loading_finished
tab.Network.loadingFailed = network_api.loading_failed
tab.start()
able()
able()
# 是否禁⽤缓存
if disable_cache:
tab.Network.setCacheDisabled(cacheDisabled=True)
tab.Page.navigate(url={你的页⾯地址})
tab.wait(10)
tab.stop()
self.browser.close_tab(tab)
# 获取的所有url详细信息
print quest_dict
监听页⾯事件
有时候特别是⼀些复杂的页⾯,页⾯依赖 js 和后端资源数据,并不是通常意义上页⾯ loadEventEnd 事件触发完就表⽰页⾯加载完成,这种情况可能需要依赖开发打点。
这⾥以开发设计了⼀个Loaded事件为例
# 具体事件注册⽅式和注册时机询问开发,所谓注册时机即要求在js对象⽣成后注册,我们项⽬中page是在⼀个js⽂件中声明的,需要等这个js⽂件请求完成后再注册
# 这边使⽤Promise⽅式,这种⽅式awaitPromise参数必须是True
js = """
new Promise((resolve, reject) => {
function(){
resolve(new Date().getTime());
});
});
"""
custom_result = tab.Runtime.evaluate(
expression=js,
awaitPromise=True,
timeout=timeout * 1000
)
print ('result').get('value')
有个坑w()获取与 chrome 开发者⼯具协议⼀样类型的时间时,这个时间不准确,只好⽤new Date().getTime()
写在最后
⼀开始是使⽤ nodejs 的 chrome-remote-interface,但是发现Page.loadEventFired回调后不会再记录请求,事实上有些页⾯仍然有请求没有完成,不懂是不是我使⽤姿势不对
附赠 W3C 的⼀幅图

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。