背景
最近刚接触PhantomJS, 听说这工具是一个基于WebKit的服务器端JavaScript API,可以实现绝大部分浏览器的操作, 迫不及待就想练练手.于是就简单写了一个程序, 简单介绍下:
需求: 通过phantomJS向一个网站发起请求, 并且记录各资源加载的时间,名字思路: 1.通过 onResourceRequested,获得资源请求时间t1, 并通过资源ID,记录在关联数组内 2.通过 onResourceReceived 获得资源响应时间t2, 也同样存到关联数组内 2.用t2 - t1得出的, 就是资源加载花费的毫秒值 3.因为资源ID是相关联的,所以只需要取任意一个关联组的url就可以
程序源码
test.js 内容如下:
// 加载库var page = require('webpage').create();var system = require('system');var req = {}; // 请求关联数组var res = {}; // 响应关联数组var num = 0; // 用于记录资源数var address = 'http://www.yaochufa.com';// 在 onResourceRequested 事件绑定动作page.onResourceRequested = function(request){ num++; req[request.id] = {'url': request.url, 'time': request.time};};// 在 onResourceReceived 事件绑定动作page.onResourceReceived = function(response){ res[response.id] = {'url': response.url, 'time': response.time};};// 开始请求page.open(address, function(status){ if(status !== 'success') { // 如果请求成功, 退出 console.log(status); phantom.exit(); } else{ console.log('主页加载完毕'); } // 根据前面记录的资源请求个数, 开始统计各资源的加载时间 for(i=1;i<=num;i++) { // 取出请求数组的资源开始时间, 并转换成时间戳 var req_time = new Date(req[i]['time']).getTime(); // 取出响应数组的资源结束时间, 并转换成时间戳 var res_time = new Date(res[i]['time']).getTime(); // 作差就是加载的时间 var diff = res_time - req_time; console.log('ID:s',i, 'Loadtime:', diff, 'ms'); // 打印对应ID的url console.log(res[i]['url']); } phantom.exit();});
问题重现
phantomjs test.js# 结果输出:ReferenceError: Can't find variable: sfCode http://qiniu-cdn7.jinxidao.com/assets/js/index.js?v=201704201643:1主页加载完毕ID:s 1 Loadtime: 73 mshttp://www.yaochufa.com/ID:s 2 Loadtime: 50 mshttp://qiniu-cdn7.jinxidao.com/assets/css/common.css?v=201704201643ID:s 3 Loadtime: 54 mshttp://qiniu-cdn7.jinxidao.com/assets/css/index.css?v=201704201643ID:s 4 Loadtime: 80 mshttp://qiniu-cdn7.jinxidao.com/assets/js/jquery.jsID:s 5 Loadtime: 78 mshttp://qiniu-cdn7.jinxidao.com/assets/js/lazyload.jsID:s 6 Loadtime: 78 mshttp://qiniu-cdn7.jinxidao.com/assets/js/common.js?v=201704201643ID:s 7 Loadtime: 150 mshttp://qiniu-cdn7.jinxidao.com/assets/js/index.js?v=201704201643ID:s 8 Loadtime: 79 mshttp://qiniu-cdn7.jinxidao.com/js/user_analytics_pc.js?v=201704201643ID:s 9 Loadtime: 154 mshttp://qiniu-cdn7.jinxidao.com/js/zhugeio_config.js?v=201704201643ID:s 10 Loadtime: 106 mshttp://cdn7.jinxidao.com/images/icon.png?v=1.3ID:s 11 Loadtime: 104 mshttp://cdn.jinxidao.com/images/icon/photo-app.pngID:s 12 Loadtime: 100 mshttp://static.anquan.org/static/outer/image/hy_124x47.pngID:s 13 Loadtime: 91 mshttp://cdn7.jinxidao.com/www/images/loading.gifID:s 14 Loadtime: 91 mshttp://cdn7.jinxidao.com/images/logo.pngID:s 15 Loadtime: 94 mshttp://cdn7.jinxidao.com/www/images/weixin_code.pngID:s 16 Loadtime: 92 mshttp://cdn7.jinxidao.com/www/images/footer-links.pngID:s 17 Loadtime: 94 mshttp://cdn7.jinxidao.com/www/images/weixin_code.png?v=1.0ID:s 18 Loadtime: 91 mshttp://cdn7.jinxidao.com/www/images/web-trust/alipay.pngID:s 19 Loadtime: 91 mshttp://cdn7.jinxidao.com/www/images/web-trust/zhizhao.jpgID:s 20 Loadtime: 93 mshttp://cdn7.jinxidao.com/www/images/web-trust/chengxin.jpgID:s 21 Loadtime: 94 mshttp://cdn7.jinxidao.com/www/images/web-trust/kexin.jpgID:s 22 Loadtime: 87 mshttp://cdn.jinxidao.com/images/white_bg.pngID:s 23 Loadtime: 91 mshttp://cdn.jinxidao.com/detail/appCode.pngID:s 24 Loadtime: 94 mshttp://cdn.jinxidao.com/detail/appCode.png?v=3ID:s 25 Loadtime: 64 mshttp://www.yaochufa.com/ajax/checkcity?v=1493041417771&callback=jQuery1113015539110638201237_1493041417766&_=1493041417767ID:s 26 Loadtime: 64 mshttp://tj.yaochufa.com/kafkahttp/kafka/log?callback=jQuery1113015539110638201237_1493041417768&event_content=http%3A%2F%2Fwww.yaochufa.com%2F&log_id=1&app_id=4&longitude=&latitude=&user_id=¤t_citycode=&upload_time=2017-04-24+21%3A43%3A37&event_time=1493041418&session_id=1493041418¤t_url=http%3A%2F%2Fwww.yaochufa.com%2F&from_url=&use_agent=Mozilla%2F5.0+(Unknown%3B+Linux+x86_64)+AppleWebKit%2F538.1+(KHTML%2C+like+Gecko)+PhantomJS%2F2.1.1+Safari%2F538.1&visit_ip=114.55.70.153&cookie=38e73c28-09f1-2945-47d8-cbd70f3d72ff&arg2=&event_id=700000&_=1493041417769ID:s 27 Loadtime: 73 mshttp://sdk.zhugeio.com/zhuge-lastest.min.js?v=2017324ID:s 28 Loadtime: 136 mshttp://hm.baidu.com/hm.js?84c5b2688d39b4e3c23d132b53b4e79bID:s 29 Loadtime: 151 mshttps://apipool.zhugeio.com/web_event/?method=web_event_srv.upload&event=%7B%22type%22%3A%20%22statis%22%2C%22sdk%22%3A%20%22web%22%2C%22sdkv%22%3A%20%221.3.0%22%2C%22cn%22%3A%20%22web%22%2C%22vn%22%3A%20%221.0%22%2C%22ak%22%3A%20%22b744d4b142c942c09cdc5095ba060824%22%2C%22did%22%3A%20%2215ba0340eac38a-04e727ec2-273f781b-c0000-15ba0340ead40b%22%2C%22ts%22%3A%201493041417.903%2C%22debug%22%3A%201%2C%22data%22%3A%20%5B%0A%20%20%20%20%7B%22et%22%3A%20%22ss%22%2C%22sid%22%3A%201493041417.902%2C%22cn%22%3A%20%22web%22%2C%22vn%22%3A%20%221.0%22%2C%22pr%22%3A%20%7B%22os%22%3A%20%22Linux%22%2C%22br%22%3A%20%22Safari%22%2C%22rs%22%3A%20%221024*768%22%2C%22url%22%3A%20%22http%3A%2F%2Fwww.yaochufa.com%2F%22%7D%7D%0A%5D%7D&_=1493041417904卡住...
前面的输出, 我们已经看到已经按照我们需求那样, 得出资源ID, 资源加载时间, 资源URL, 但是很奇怪的事, 到了大概是30的时候, 就卡住了, 这里肯定不是程序运行完, 因为程序结尾有个退出, 如果是正常结束了, 应该就退出了.所以我觉得这个肯定有哪里出错了! 这也是让我觉得phantomJS很不好的地方, 报错也不给个明显的错误提示, 就在哪里一动不动的.
既然phantomJS不明显报错, 咱们只能一步步调试了, 之前谷歌上看到, phantomJS能够用debug模式运行, 然后通过访问特定端口来用浏览器调试, 但是在这不行, 因为浏览器压根打不开那个链接, 一直在转, 也不知道是什么原因. 所以我们这里只能用console.log来排错了! 因为打印输出的代码是在最后面的, 那么可以证明前面的事件是正确被执行的, 因为如果前面的事件失败了, 那么整个程序肯定是不会打印的,相反的而是会停在前面卡住了.为了证明这一点, 咱们把for循环去掉, 源码不再重复, 直接贴结果:[root@iZ23pynfq19Z phantomjs-2.1.1-linux-x86_64]# ./bin/phantomjs 4.js ReferenceError: Can't find variable: sfCode http://qiniu-cdn7.jinxidao.com/assets/js/index.js?v=201704201643:1主页加载完毕
嗖的一声就结束了, 这就验证了我们的猜想, 出错的代码应该是在for的打印里面, 既然知道大概的访问, 咱们for里面的每句代码都注释掉, 一句句单独执行, for循环体代码分别是:
for(i=1;i<=num;i++) { // 取出请求数组的资源开始时间, 并转换成时间戳 var req_time = new Date(req[i]['time']).getTime(); } phantom.exit();---- 试验1结果: 程序顺利输出并结束 ---- for(i=1;i<=num;i++) { // 取出响应数组的资源结束时间, 并转换成时间戳 var res_time = new Date(res[i]['time']).getTime(); } phantom.exit(); ---- 试验2结果: 程序卡住了!! ----
可以看出, 问题应该是出现在第二句获取时间的地方, 但是这里应该是没问题的, 因为在实验时, 别的时间都能顺利转换! 但是没办法, 只有试验才能给出证据! 我们将这句话拆分两句运行
for(i=1;i<=num;i++) { // 取出响应数组的资源结束时间, 并转换成时间戳 var res_time = new Date(res[i]['time']) console.log(res_time); console.log(res_time.getTime()); } phantom.exit(); ## 程序输出:...Mon Apr 24 2017 22:18:58 GMT+0800 (CST)1493043538945Mon Apr 24 2017 22:18:58 GMT+0800 (CST)1493043538932Mon Apr 24 2017 22:18:58 GMT+0800 (CST)1493043538946Mon Apr 24 2017 22:18:59 GMT+0800 (CST)1493043539087卡住了...
看来问题真的出现在这问题, 但是这样还是不能看出问题啊, 因为变量都正常输出了! 思来想去都觉得找不到问题, 只能继续往上拆了, 它是通过i在响应关联数组取出对象, 所以我们在上面加一句打印对象:
for(i=1;i<=num;i++) { // 取出响应数组的资源结束时间, 并转换成时间戳 console.log(res[i]['time']); var res_time = new Date(res[i]['time']) console.log(res_time); console.log(res_time.getTime()); } phantom.exit(); ## 程序输出:...Mon Apr 24 2017 22:26:21 GMT+0800 (CST)1493043981129{"url":"https://apipool.zhugeio.com/web_event/?method=web_event_srv.upload&event=%7B%22type%22%3A%20%22statis%22%2C%22sdk%22%3A%20%22web%22%2C%22sdkv%22%3A%20%221.3.0%22%2C%22cn%22%3A%20%22web%22%2C%22vn%22%3A%20%221.0%22%2C%22ak%22%3A%20%22b744d4b142c942c09cdc5095ba060824%22%2C%22did%22%3A%20%2215ba05b2b444b7-0c599ec6d-273f781b-c0000-15ba05b2b454b1%22%2C%22ts%22%3A%201493043981.128%2C%22debug%22%3A%201%2C%22data%22%3A%20%5B%0A%20%20%20%20%7B%22et%22%3A%20%22ss%22%2C%22sid%22%3A%201493043981.126%2C%22cn%22%3A%20%22web%22%2C%22vn%22%3A%20%221.0%22%2C%22pr%22%3A%20%7B%22os%22%3A%20%22Linux%22%2C%22br%22%3A%20%22Safari%22%2C%22rs%22%3A%20%221024*768%22%2C%22url%22%3A%20%22http%3A%2F%2Fwww.yaochufa.com%2F%22%7D%7D%0A%5D%7D&_=1493043981128","time":"2017-04-24T14:26:21.273Z"}Mon Apr 24 2017 22:26:21 GMT+0800 (CST)1493043981273Mon Apr 24 2017 22:26:21 GMT+0800 (CST)1493043981132undefined程序卡住..
找到原因
惊喜发现undefined, 嗯! 应该是undefined没有getTime方法, 导致程序出错了.看了下对应的资源ID是30, 而对应的url是:https://apipool.zhugeio.com, 不过为什么这个对象是undefined呢? 而请求资源那块确实是有的, 谷歌了下, 发现这个是一个资源监控网站, 也就是类似帮别人测试资源加载速度的, 难道这个请求是没有返回的? 先不管, 既然它是undefined. 那就通过判断过滤掉吧:
for(i=1;i<=num;i++) { // 取出请求数组的资源开始时间, 并转换成时间戳 var req_time = new Date(req[i]['time']).getTime(); // 过滤undefned if(res[i]) { // 取出响应数组的资源结束时间, 并转换成时间戳 var res_time = new Date(res[i]['time']).getTime(); // 作差就是加载的时间 var diff = res_time - req_time; console.log('ID: ',i, 'Loadtime:', diff, 'ms'); // 打印对应ID的url console.log(res[i]['url']); } else { console.log('ID: ' + i + ' no response'); } } --- 程序输出 ---.....ID: 30 no responseID: 31 Loadtime: 148 mshttps://apipool.zhugeio.com/web_event/?method=web_event_srv.upload&event=%7B%22type%22%3A%20%22statis%22%2C%22sdk%22%3A%20%22web%22%2C%22sdkv%22%3A%20%221.3.0%22%2C%22cn%22%3A%20%22web%22%2C%22vn%22%3A%20%221.0%22%2C%22ak%22%3A%20%22b744d4b142c942c09cdc5095ba060824%22%2C%22did%22%3A%20%2215ba0698d3a9f4-0797b7d87-273f781b-c0000-15ba0698d3b4b2%22%2C%22ts%22%3A%201493044923.712%2C%22debug%22%3A%201%2C%22data%22%3A%20%5B%0A%20%20%20%20%7B%22et%22%3A%20%22info%22%2C%22pr%22%3A%20%7B%22os%22%3A%20%22Linux%22%2C%22br%22%3A%20%22Safari%22%2C%22rs%22%3A%20%221024*768%22%2C%22url%22%3A%20%22http%3A%2F%2Fwww.yaochufa.com%2F%22%2C%22cn%22%3A%20%22web%22%2C%22vn%22%3A%20%221.0%22%7D%7D%0A%5D%7D&_=1493044923712ID: 32 Loadtime: 37 ms.....
可以看到, 这个脚本可以正常运行了.
不过虽然可以运行了, 还是很好奇是不是真的有资源只有请求, 而没有响应的, 然而事实并不是! 在后面的测试中, 我发现, 如果我们在onResourceReceived打印资源ID和对象时, 会发现, 咱们前面丢失的30号对象, 也是有输出的, 咱们来试下:page.onResourceReceived = function(response){ console.log('ID: ' + response.id); // 将对象转变成JSON字符串 console.log(JSON.stringify(response)); res[response.id] = {'url': response.url, 'time': response.time};};--- 程序输出 ---....(前面打印耗时)// 单独打印ID: 30{"contentType":null,"headers":[],"id":30,"redirectURL":null,"stage":"end","status":null,"statusText":null,"time":"2017-04-24T14:52:08.402Z","url":"https://api.zhugeio.com/v1/events/codeless/appkey/b744d4b142c942c09cdc5095ba060824/platform/3?event_url=%2F"}...
可以看出, 资源ID:30是有响应的, 只是响应的比较慢而已, 当开始运行循环体时, 它还没完成写, 因为JS时众所周知的异步编程, 所以它并不像我们一般程序那样顺序执行, 而是通过回调的方式完成任务.
代码小优化
既然知道它也是有响应的的, 那么咱们就不能抛弃它! 因为它也是我们的一份子! 但是我们该怎么做呢? 无奈之下去看PhantomJS的官网, 看到一个示范例子里面用到一个事件:onLoadFinished, 字面意思就是完成加载时, 这个看起来就是我们要找的, 因为如果页面加载完毕, 那么资源方面肯定是已经收齐(菜鸟理解), 那我们开始改造刚才的脚本的, 将脚本的for分离出来, 放到 onLoadFinished 事件中, 代码如下:
page.onLoadFinished = function(status){ console.log('加载完毕, 状态: ' + status); for(var i=1;i<=num;i++) { var req_time = new Date(result[i].time).getTime(); if (rest[i]) { var rest_time = new Date(rest[i].time).getTime(); var diff = rest_time - req_time; console.log('ID:s',i, 'Loadtime:', diff, 'ms'); } else // 颜色提醒 console.log('\033[31mNo such response!\033[0m'); console.log(result[i].url); }};
经过上面的改造, 脚本已经能够比较好的算出加载时间了,不过咱们有些地方还需要优化, 那就是输入地址那边, 因为地址是写死的, 所以如果我们用来测试别的站点时, 就必须要修改代码, 这简直就是一个大错误! 所以我们需要system库!, 完整代码如下:
// 加载库var page = require('webpage').create();var system = require('system');var req = {}; // 请求关联数组var res = {}; // 响应关联数组var num = 0; // 用于记录资源数var args = system.args;if (args.length !== 2){ console.log('\033[31mUsage: Url ( http://www.baidu.com )\033[0m'); phantom.exit();}var address = args[1];// 在 onResourceRequested 事件绑定动作page.onResourceRequested = function(request){ num++; req[request.id] = {'url': request.url, 'time': request.time};};// 在 onResourceReceived 事件绑定动作page.onResourceReceived = function(response){ res[response.id] = {'url': response.url, 'time': response.time};};page.onLoadFinished = function(status){ console.log('加载完毕, 状态: ' + status); // 根据前面记录的资源请求个数, 开始统计各资源的加载时间 for(var i=1;i<=num;i++) { var req_time = new Date(req[i].time).getTime(); if (res[i]) { var rest_time = new Date(res[i].time).getTime(); var diff = rest_time - req_time; console.log('ID:s',i, 'Loadtime:', diff, 'ms'); } else console.log('\033[31mNo such response! ID: ' + i + '\033[0m'); console.log(req[i].url); }};// 开始请求page.open(address, function(status){ if(status !== 'success') { // 如果请求成功, 退出 console.log(status); phantom.exit(); } else{ console.log('主页加载完毕'); phantom.exit(); }});
不过还是有时候还是会出现找不到那个响应慢的请求, 可能需要换种实现的思路或者更加贴近的事件, 不过这个用来练手真是感觉, 之前一直觉得PhantomJS卡死, 其实只是程序错了, 这次对phantomJS改观了, 这是一个不错的工具, 往后还会继续深入学习!
欢迎各位大神指点交流,转载请注明来源: