Alex

有的故事值得一直说下去.
Home » Latest Posts

一、通过指定标签跳转到指定url

{
"id": 1,
"method": "Page.navigate",
"params":{"url": "https://www.baidu.com"}

}

二、获得页面html

{
'id': 21,
'method': 'DOM.getDocument',
'params': {'depth': 0, ' pierce': true}

}

三,获得当前标签页html

{ "id": 21, "method": "DOM.getOuterHTML", "params": {"backendNodeId": 1} }

1、?问题描述 我们在使用yarn命令对项目进行打包时,可能会出现如下的错误信息 “FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory”

image

2、?解决办法 此时我们可以使用increase-memory-limit插件进行解决。

increase-memory-limit插件官网:https://www.npmjs.com/package/increase-memory-limit

全局安装increase-memory-limit插件

npm install -g increase-memory-limit 然后在项目根目录下执行如下命令

increase-memory-limit 最后我们再使用yarn命令进行打包即可成功(亲测有效)。

Charles-MQ 【转】chrome devtools protocol——Web 性能自动化 前言 在测试Web页面加载时间时,可能会是这样的:

打开chrome浏览器。 按F12打开开发者工具。 在浏览器上打开要测试的页面 查看开发者工具中Network面板的页面性能数据并记录 或者在开发者工具中Console面板运行performance.timing和performance.getEntries()收集数据 performance相关信息看这里PerformanceTiming

几十上百个页面,每个版本都这样来,估计疯了,所以就想怎么把它做成自动化呢?

chrome devtools protocol chrome devtools protocol允许第三方对基于chrome的web应用程序进行调试、分析等,它基于WebSocket,利用WebSocket建立连接DevTools和浏览器内核的快速数据通道。一句话,有了这个协议就可以自己开发工具获取chrome的数据

协议详细内容看这里chrome devtools protocol

目前已经有很多大神针对这个协议封装出不同语言(nodejs,python,java...)的库,详细信息看这里awesome-chrome-devtools

这边我选择的是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:

Network.requestWillBeSent每个http请求发送前回调 Network.responseReceived首次接送到http响应时回调 Network.loadingFinished请求加载完成时回调 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的一幅图

现如今大多数页面,通过html5/js等方式,动态渲染页面,对于抓取动态网页,用常规的抓取方法显得力不从心。 前些年出现了phantomjs,可以有效的抓取动态页面,但phantomjs的一些缺点,内存溢出等经常出现卡死。现在该作者也停止更新phantomjs了

Now,决定弃用phantomjs!

发现新大陆

chrome自从v59版本后,推出了headless浏览器,配合Chrome DevTools Protocol,使用浏览器内核其Api,可实现分布远程调试chrome(数据抓取等)

Chrome DevTools Protocol允许工具对Chromium,Chrome和其他基于Blink的浏览器进行测试,检查,调试和配置。 许多现有项目目前使用该协议。 Chrome DevTools开发人员工具,使用此协议,团队维护其API。

Server端,在装有chrome浏览器环境的服务器中,打开chrome remote debug 以下命令在docker环境下,alpine,chrome环境中,更多chrome启动参数,参考https://peter.sh/experiments/chromium-command-line-switches/

chromium-browser --headless --no-sandbox --disable-gpu --remote-debugging-port=9222

chrome --headless --no-sandbox --disable-gpu --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0 --window-size=1920,1080 --user-data-dir=

注意,这里使用的remote-debugging-port是9444,是在初始化启动命令中设置折。可以通过浏览器打开查看远程服务器中的chrome信息

http://192.168.110.128:9444/json

[ { "description": "", "devtoolsFrontendUrl": "/devtools/inspector.html?ws=192.168.110.128:9444/devtools/page/(9E4790959AAB0C8FB8F309ABB204729C)", "id": "(9E4790959AAB0C8FB8F309ABB204729C)", "title": "百度一下,你就知道", "type": "page", "url": "https://www.baidu.com/", "webSocketDebuggerUrl": "ws://192.168.110.128:9444/devtools/page/(9E4790959AAB0C8FB8F309ABB204729C)" }, { "description": "", "devtoolsFrontendUrl": "/devtools/inspector.html?ws=192.168.110.128:9444/devtools/page/(C8A6E4D304F820AC9F48AC9A34137F78)", "id": "(C8A6E4D304F820AC9F48AC9A34137F78)", "title": "百度一下,你就知道", "type": "page", "url": "https://www.baidu.com/", "webSocketDebuggerUrl": "ws://192.168.110.128:9444/devtools/page/(C8A6E4D304F820AC9F48AC9A34137F78)" }, { "description": "", "devtoolsFrontendUrl": "/devtools/inspector.html?ws=192.168.110.128:9444/devtools/page/(E18749BAD4802F598A844A7EE14BA9C4)", "id": "(E18749BAD4802F598A844A7EE14BA9C4)", "title": "about:blank", "type": "page", "url": "about:blank", "webSocketDebuggerUrl": "ws://192.168.110.128:9444/devtools/page/(E18749BAD4802F598A844A7EE14BA9C4)" }, { "description": "", "devtoolsFrontendUrl": "/devtools/inspector.html?ws=192.168.110.128:9444/devtools/page/(2C5CCAACD2BFBA9E39D73EBAB2291C87)", "id": "(2C5CCAACD2BFBA9E39D73EBAB2291C87)", "title": "", "type": "page", "url": "file:///", "webSocketDebuggerUrl": "ws://192.168.110.128:9444/devtools/page/(2C5CCAACD2BFBA9E39D73EBAB2291C87)" } ] 新建一个标签 http://localhost:9222/json/new http://localhost:9222/json/new?http://www.baidu.com 关闭一个标签 http://localhost:9222/json/close/477810FF-323E-44C5-997C-89B7FAC7B158 激活标签页 http://localhost:9222/json/activate/477810FF-323E-44C5-997C-89B7FAC7B158 查看版本信息 http://localhost:9222/json/version client端,通过websocket协议,连接至chrome remote port ws://192.168.110.128:9444/devtools/page/(9E4790959AAB0C8FB8F309ABB204729C) 执行以下api接口中的命令

#打开页面 {"id":200,"method":"Page.navigate","params":{"url":"https://www.baidu.com"}} #获取dom {"id":200,"method":"DOM.getDocument"} #获取html {"id":200,"method":"DOM.getOuterHTML","params":{"nodeId":1,"backendNodeId":12}} #获取资源树 {"id":200,"method":"Page.getResourceTree","params":{}} 通过Api接口(Runtime.evaluate)执行js,类似于chrome中的onsole输出 {"id":200,"method":"Runtime.evaluate","params":{"expression":"document.title","objectGroup":"console","includeCommandLineAPI":true,"silent":false,"contextId":1,"returnByValue":false,"generatePreview":true,"userGesture":true,"awaitPromise":false}}

{"id":200,"method":"Runtime.evaluate","params":{"expression":"document.title","objectGroup":"console","includeCommandLineAPI":true,"silent":false,"returnByValue":false,"generatePreview":true,"userGesture":true,"awaitPromise":false}}

返回结果 { "id": 200, "result": { "result": { "type": "string", "value": "百度一下,你就知道" } } }

Api功能模块域 https://chromedevtools.github.io/debugger-protocol-viewer/1-2/ 扩展API 有很多扩展应用使用了该协议来与页面做交互调试,官网上有很多Sample Extensions

https://developer.chrome.com/extensions/samples#search:debugger Chrome Api https://chromedevtools.github.io/devtools-protocol/

API–模拟键盘输入 https://chromedevtools.github.io/devtools-protocol/tot/Input/

chrome启动参数 https://peter.sh/experiments/chromium-command-line-switches/

一些有意思的工具 https://developer.chrome.com/devtools/docs/debugging-clients

后话 很多工具都使用了Chrome debugging protocol,包括phantomJS,Selenium的ChromeDriver,本质都是一样的实现,它就相当于Chrome内核提供的API让应用调用。

官网列出了很多有意思的工具:链接,因为API丰富,所以才有了这么多的chrome插件。

实现了Remote debugging protocol的node的库:

chrome-debug-protocol 使用了ES6和TypeScript https://github.com/DickvdBrink/chrome-debug-protocol chrome-remote-interface 官网推荐的 https://github.com/cyrus-and/chrome-remote-interface chrome-har-capturer 传入url,直接获取har format文件 https://github.com/cyrus-and/chrome-har-capturer

什么是WebDriver WebDriver是一个开源工具,用于在许多浏览器上自动测试web应用。它提供了导航到网页,用户输入,JavaScript执行等功能。 WebDriver W3C标准 https://w3c.github.io/webdriver/webdriver-spec.html

什么是chromedriver ChromeDriver是一个独立的服务,它为Chromium实现WebDriver’s wire protocol 协议 chromedriver正在实施并转向W3C标准。ChromeDriver适用于Android版Chrome和桌面版Chrome(Mac,Linux,Windows和ChromeOS)。

chromedriver已经实现的w3c标准功能 https://chromium.googlesource.com/chromium/src/+/master/docs/chromedriver_status.md

chromedriver由chromium team维护

使用Selenium驱动chromedriver import time #导入webdriver from selenium import webdriver

#指定chromedriver的path位置 driver = webdriver.Chrome('/path/to/chromedriver') # Optional argument, if not specified will search path. driver.get('http://www.google.com/xhtml'); time.sleep(5) # Let the user actually see something! search_box = driver.find_element_by_name('q') search_box.send_keys('ChromeDriver') search_box.submit() time.sleep(5) # Let the user actually see something! driver.quit() 控制chromedriver的生命周期 Controlling ChromeDriver’s lifetime ChromeDriver类在创建时启动ChromeDriver服务器进程,并在调用退出时终止它。 这可能会浪费大量时间用于大型测试套件,其中每个测试都会创建一个ChromeDriver实例。

有两种方法可以解决这个问题:

Use the ChromeDriverService. This is available for most languages and allows you to start/stop the ChromeDriver server yourself. See here for a Java example (with JUnit 4): ``` @RunWith(BlockJUnit4ClassRunner.class) public class ChromeTest extends TestCase { private static ChromeDriverService service; private WebDriver driver;

@BeforeClass public static void createAndStartService() { service = new ChromeDriverService.Builder() .usingDriverExecutable(new File(“path/to/my/chromedriver”)) .usingAnyFreePort() .build(); service.start(); }

@AfterClass public static void createAndStopService() { service.stop(); }

@Before public void createDriver() { driver = new RemoteWebDriver(service.getUrl(), DesiredCapabilities.chrome()); }

@After public void quitDriver() { driver.quit(); }

@Test public void testGoogleSearch() { driver.get(“http://www.google.com”); // rest of the test… } }

python : import time

from selenium import webdriver import selenium.webdriver.chrome.service as service

service = service.Service(‘/path/to/chromedriver’) service.start() capabilities = {‘chrome.binary’: ‘/path/to/custom/chrome’} driver = webdriver.Remote(service.service_url, capabilities) driver.get(‘http://www.google.com/xhtml’); time.sleep(5) # Let the user actually see something! driver.quit()

  1. Start the ChromeDriver server separately before running your tests, and connect to it using the Remote WebDriver. Terminal: $ ./chromedriver Started ChromeDriver port=9515 version=14.0.836.0

java: WebDriver driver = new RemoteWebDriver(“http://127.0.0.1:9515”, DesiredCapabilities.chrome()); driver.get(“http://www.google.com”); ```

https://div.io/topic/1464 https://sites.google.com/a/chromium.org/chromedriver/ https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol

https://github.com/seleniumhq/selenium https://sites.google.com/a/chromium.org/chromedriver/getting-started

https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities.md https://sites.google.com/a/chromium.org/chromedriver/capabilities http://peter.sh/examples/?/chromium-switches.html

  1. 白色:FFFFFF
  2. 红色:FF0000
  3. 绿色:00FF00
  4. 蓝色:0000FF
  5. 洋红:FF00FF
  6. 墨绿:00FFFF
  7. 黄色:FFFF00
  8. 黑色:000000
  9. 爱丽丝兰:F0F8FF
  10. 碧燃昌绿:70DB93
  11. 巧克力色:5C3317
  12. 蓝紫色:9F5F9F
  13. 黄铜:B5A642
  14. 亮金:D9D919
  15. 褐色:A62AA2
  16. 青铜:8C7853
  17. 青铜2:A67D3D
  18. 藏青:5F9F9F
  19. 亮铜:D98719
  20. 铜色:B87333
  21. 珊瑚色:FF7F00
  22. 矢车菊兰:42426F
  23. 深褐色:5C4033
  24. 深绿色:2F4F2F
  25. 深铜绿色:4A766E
  26. 深橄榄绿:4F4F2F
  27. 紫色:9932CD
  28. 深紫色:871F78
  29. 深石板蓝:6B238E
  30. 深石板灰:2F4F4F
  31. 深黄褐色:97694F
  32. 深蓝玉色:7093DB
  33. 暗木色:855E42
  34. 暗灰:545454
  35. 暗玫瑰色:856363
  36. 长石色:D19275
  37. 砖红色:8E2323
  38. 草绿:238E23
  39. 金色:CD7F32
  40. 秋叶色:DBDB70
  41. 灰色:C0C0C0
  42. 铜绿色:527F76
  43. 黄绿色:93DB70
  44. 军绿:215E21
  45. 印第安红色:4E2F2F
  46. 土黄:9F9F5F
  47. 浅蓝:C0D9D9
  48. 浅灰:A8A8A8
  49. 浅铜蓝:8F8FBD
  50. 浅木色:E9C2A6
  51. 浅绿:32CD32
  52. 橙色:E47833
  53. 栗色:8E236B
  54. 中绿:32CD99
  55. 中蓝:3232CD
  56. 中草绿:6B8E23
  57. 中秋叶色:EAEAAE
  58. 中紫色:9370DB
  59. 中海绿:426F42
  60. 中石板蓝:7F00FF
  61. 中春绿:7FFF00
  62. 中蓝玉色:70DBDB
  63. 中紫红色:DB7093
  64. 中木色:A68064
  65. 夜蓝色:2F2F4F
  66. 海蓝色:23238E
  67. 氖蓝色:4D4DFF
  68. 氖粉红色:FF6EC7
  69. 新夜蓝色:00009C
  70. 新黄褐色:EBC79E
  71. 暗金色:CFB53B
  72. 橘色:FF7F00
  73. 橘红:FF2400
  74. 淡紫:DB70DB
  75. 淡绿:8FBC8F
  76. 粉红:BC8F8F
  77. 棕色:EAADEA
  78. 石英色:D9D9F3
  79. 富兰色:5959AB
  80. 橙红色:6F4242
  81. 猩红:8C1717
  82. 海绿:238E68
  83. 半甜巧克力色:6B4226
  84. 赭色:8E6B23
  85. 银色:E6E8FA
  86. 天蓝:3299CC
  87. 石板蓝:007FFF
  88. 香粉红:FF1CAE
  89. 春绿:00FF7F
  90. 钢蓝:236B8E
  91. 夏天的天空:38B0DE
  92. 黄褐色:DB9370
  93. 蓝玉色:ADEAEA
  94. 暗褐色:5C4033
  95. 亮灰:CDCDCD
  96. 紫罗兰色:4F2F4F
  97. 紫红:CC3299
  98. 麦色:D8d8BF
  99. 暗黄:99CC32
Life is fantastic
🥕 More