在京东网站前端监控平台的特等实践,phantomjs制作nodejs的小说爬虫

PhantomJS 和 NodeJS 在京东网站前端监控平台的一流实践

2016/11/21 · JavaScript
· NodeJS,
phantomjs

本文小编: 伯乐在线 –
keelii
。未经小编许可,禁止转发!
欢迎插足伯乐在线 专辑小编。

澳门葡京 1

内容要点

一步一脚印兑现一个爬虫,后边的内容相比简单,有询问可以直接跳过同时作品内容较长和多图,指出在pc下阅读
源码地址
phantomjs捕获内容
详见介绍通过async.mapLimit并发处理,结合定时器举行延时执行
澳门葡京 ,数量存放到mongodb
多少输出成文件
(如有错误请大家提议,一起上学)

每一天都会暴发新的代码、用户测试工具和框架。上边的列表列出了可以达成种种测试要求的代码工具。你应该调查研商一下,看这一个工具是不是适用于您的技术栈和技艺要求。

为什么须要一个前端监控系统

一般而言在一个大型的 Web 项目中有为数不少监察,比如后端的服务 API
监控,接口存活、调用、延迟等监督,那么些相似都用来监控后台接口数据层面的音讯。而且对于大型网站系统的话,从后端服务到前台体现会有众多层:内网
VIP、CDN
等。可是那个监督并不可以精确地影响用户看到的前端页面状态,比如:页面第三方系统数据调用失败,模块加载极度,数据不正确,空白开天窗等。那时候就须求在此从前端
DOM
呈现的角度去分析和采访用户真正看到的事物,从而检测出页面是或不是出现格外难题

PhantomJS是一款webkit内核的headelsss的浏览器,使用QtWebkit,
援助DOM操作、CSS选择器、JSON、Canvas和SVG,可以萧规曹随浏览器的劳务。

介绍(有询问可以直接跳过)

关于PhantomJS
首先介绍一下phantomjs

PhantomJS是一个基于WebKit的服务器端JavaScript API,它按照BSD开源协议揭橥。PhantomJS无需浏览器的支撑即可兑现对Web的支撑,且原生扶助各样Web标准,如DOM
处理、JavaScript、CSS接纳器、JSON、Canvas和可缩放矢量图形SVG。PhantomJS重若是透过JavaScript和CoffeeScript控制WebKit的CSS接纳器、可缩放矢量图形SVG和HTTP网络等各种模块。

  1. Jasmine

须求监控连串缓解的题材

页面经常出现以下难题时必要使用邮件、短信公告有关人口修复难题

  • 状态码重临错误(50x, 40x)不能开拓
  • 模块加载失败
  • 页面乱码
  • 数码科学

接触报警时要有实地快照,以便复现难点

 

phantomjs的应用场景

不必浏览器的Web测试:无需浏览器的场地下进展疾速的Web测试,且支持广大测试框架,如YUI
Test、Jasmine、WebDriver、Capybara、QUnit、Mocha等。
页面自动化操作:使用专业的DOM
API或部分JavaScript框架(如jQuery)访问和操作Web页面。
显示屏捕获:以编程方式抓起CSS、SVG和Canvas等页面内容,即可完毕互联网爬虫应用。打造服务端Web图形应用,如截图服务、矢量光栅图应用。
网络监督:自动进行网络质量监控、跟踪页面加载意况以及将有关监督的消息以专业的HAR格式导出。


根据phantomjs2.0拓展落到实处有二种完毕方案,一种是利用基于全局的
http://phantomjs.org/
,别的一种是包装的模块 phantom – 法斯特 NodeJS API for PhantomJS
-https://github.com/amir20/phantomjs-node
在京东网站前端监控平台的特等实践,phantomjs制作nodejs的小说爬虫。那里选取phantomjs-node
有关phantomjs-node的装置以及入门
拔取可以依照百度前端高校2017中的网页抓取分析服务多元连锁内容中读书,那里放一下事先phantomjs-node
读书的笔记和demo
中的phantomjs_1~4目录下
后文也会愈发求证使用情势。

澳门葡京 2

技能选型

督查的意思和回归测试的在真相上是一致的,都是对已上线功用进行回归测试,但差距的是监督须求做深刻的可不止可轮回的回归测试,而测试唯有要求在上线之后做一遍回归

既是监控和测试的精神一致,那大家一齐可以利用测试的艺术来做监控连串。在自动化测试技术随处开花的一时,不乏很多好用的自动化工具,大家只要求把那么些自动化工具进行整合为大家所用即可

  • NodeJS – 越发适用于互连网密集型任务
  • PhantomJS – 模拟无界面的浏览器,提供丰盛的基业交互 API

安装

落到实处思路和进度

Jasmine 是一个行为使得的测试开发框架,用于对 JavaScript
代码进行测试。它不借助其余任何 JavaScript 框架,也不须求DOM。它的语法简洁、明确,写测试卓殊简单。

NodeJS

NodeJS 是一个 JavaScript 运行条件,非阻塞 I/O
和异步、事件驱动,这几点对于大家打造基于 DOM 元素的督察是丰盛首要的

mac同学利用 brew install casperjs

落实思路

phantomjs就一定于一个无图形界面的浏览器,那么大家提供连接给phantomjs就表示大家能得到那些url的内容。
本次爬虫的内容是梦想获获得随笔的保有章节以及其情节,直接以笔阁网为例,因为本次爬虫是直接爬笔阁网的。
大家开拓http://www.qu.la/book/5443,

![Uploading 14995025771427_828375.jpg . . .]## 内容要点
一步一脚印落到实处一个爬虫,小说内容较长,提出在pc下阅读
源码地址
phantomjs捕获内容
详细介绍通过async.mapLimit并发处理,结合定时器举办延时执行
数据存放到mongodb
数量输出成文件
(如有错误请我们提出,一起学习)

  1. Mocha

PhantomJS

PhantomJS 是一个按照 webkit 的浏览器引擎,可以选用 JavaScript API
来模拟浏览器的操作。它使用 QtWebKit 作为它的浏览器主旨,使用 webkit
来编译解释实施 JavaScript 代码。也就是说任何你可以在 webkit
浏览器里做的事情,它都能一呵而就

它不只是个暗藏的浏览器,提供了诸如 CSS 接纳器、帮忙 Web 标准、DOM
操作、JSON、HTML5、Canvas、SVG 等,同时也提供了拍卖公事 I/O
的操作等。PhantomJS
的用途可谓非凡广泛,诸如互联网监测、网页截屏、无浏览器的 Web
测试、页面访问自动化等

何以不是 Selenium

做自动化测试的同室肯定都知晓 Selenium。可以使用 Selenium
将测试用例在浏览器中举办,而且 Selenium
对各个平台和广阔浏览器帮助相比较好,可是 Selenium
上手难度全面略高,而且采取Selenium 必要在劳务器端安装浏览器

考虑到监控重点职务在监控不在测试。系统并不需求太多着想包容性,而且监控功效相对单一,紧要对页面实行职能上的回归测试,所以采取了
PhantomJS

 

介绍(有打探可以一贯跳过)

关于PhantomJS
先是介绍一下phantomjs

PhantomJS是一个基于WebKit的服务器端JavaScript API,它根据BSD开源协议宣布。PhantomJS无需浏览器的支撑即可兑现对Web的支撑,且原生接济各类Web标准,如DOM
处理、JavaScript、CSS拔取器、JSON、Canvas和可缩放矢量图形SVG。PhantomJS首若是通过JavaScript和CoffeeScript控制WebKit的CSS选拔器、可缩放矢量图形SVG和HTTP互连网等相继模块。

Mocha 是一个成效充足的 JavaScript 测试框架,既运行于 Node.js
环境中,也可以运行于浏览器环境中。Mocha
以串行方式运行测试,能做出灵活而规范的报告,也能将测试中未捕捉的百般映射到科学的测试用例。

架构设计

可以做哪些?

phantomjs的使用场景

不必浏览器的Web测试:无需浏览器的情况下展开高效的Web测试,且支持广大测试框架,如YUI
Test、Jasmine、WebDriver、Capybara、QUnit、Mocha等。
页面自动化操作:使用标准的DOM
API或部分JavaScript框架(如jQuery)访问和操作Web页面。
显示屏捕获:以编程格局抓起CSS、SVG和Canvas等页面内容,即可完毕网络爬虫应用。创设服务端Web图形应用,如截图服务、矢量光栅图应用。
网络监督:自动进行网络质量监控、跟踪页面加载情状以及将有关监督的音信以规范的HAR格式导出。


据悉phantomjs2.0展开落到实处有三种完结方案,一种是应用基于全局的
http://phantomjs.org/
,此外一种是包装的模块 phantom – 法斯特 NodeJS API for PhantomJS
-https://github.com/amir20/phantomjs-node
此处选择phantomjs-node
关于phantomjs-node的装置以及入门
使用可以根据百度前端高校2017中的网页抓取分析服务多元相关内容中读书,这里放一下以前phantomjs-node
学学的笔记和demo
中的phantomjs_1~4目录下
后文也会尤其求证使用形式。

澳门葡京 3

架构概览

澳门葡京 4

  1. Headless的网站集成测试

完毕思路和进程

  1. Chai

架构简述

对于 DOM 监控服务,在应用规模上拓展了垂直细分:

  • 平整管理连串
  • 规则队列生成器
  • 长时持续处理器
  • PhantomJS 服务
  • 服务化 API

在使用规模上开展的垂直细分能够对选择做分布式安插,进步处理能力。中期也有益于做品质优化、系统改造扩张等

可以和单元测试框架如贾斯敏、Mocha和WebDriver集成

落到实处思路

phantomjs就一定于一个无图形界面的浏览器,那么大家提供连接给phantomjs就意味着大家能博得这些url的始末。
这一次爬虫的始末是意在得到到小说的拥有章节以及其内容,直接以笔阁网为例,因为本次爬虫是向来爬笔阁网的。
咱俩开辟http://www.qu.la/book/5443,

14995025771427.jpg

地点就有那本小说的许多章节,所以就有了第一步,或者那一个页面上有所章节,通过”开发者工具”中的检查共成效

14995028395237.jpg

咱俩得以看来知道内容是那般的结构

<div id ="list">
<dd>
<a href="/**">第xx章</a>
</dd>
....
</div>

故此只要大家赢得 id为list
中保有的dd,就获得了随笔的享有章节,同时经过dd中a标签的href属性就足以连接到拥有章节的内容。

爬虫方面的笔触表明到那边

Chai 是个协助 BDD / TDD 的库,可用以
node 和浏览器,可同盟其他 JavaScript
测试框架使用。

缓解方案

  1. 屏幕捕捉

兑现进度

(请确保node版本高于7.9,本文基于7.10.0)
(最好先明白es7中async/await 以及child_process)
什么行使phantomjs-nodejs

何以运行代码?。。
将代码保存在一个js文件中诸如test.js
然后运行

node test.js

自己的栗子

const phantom = require('phantom');//导入模块
//async解决回调问题,es7的内容
(async function() {
     // await解决回调问题,创建一个phantom实例
    const instance = await phantom.create();
    //通过phantom实例创建一个page对象,page对象可以理解成一个对页面发起请求和处理结果这一集合的对象
    const page = await instance.createPage();
    //页面指向的是哪个一个url
    await page.on("onResourceRequested", function(requestData) {
        console.info('Requesting', requestData.url)
    });
  //得到打开该页面的状态码
    const status = await page.open('https://stackoverflow.com/');
    console.log(status);
//输出该页面的内容
    const content = await page.property('content');
    console.log(content);
    //输出内容
   //退出该phantom实例
    await instance.exit();
}());

输出结果

14995107245755.jpg

自然不容许间接行使那么些内容,所以就须要经过

//这个方法,我的理解是跟你在chrome中的输出台的操作是一样的所以看看下面栗子
await page.evaluate(function() {});

const phantom = require('phantom');
let url = encodeURI(`https://www.baidu.com/s?wd="hello"`);
(async function() {
    const instance = await phantom.create();
    const page = await instance.createPage();
    const status = await page.open(url);
    if (status !== 'success') {
        console.log("访问失败");
        return;
    } else {
        let start = Date.now();
        let result = await page.evaluate(function() {
            return document.title
        });
        let data = {
            cose: 1,
            msg: "抓取成功",
            time: Date.now() - start,
            dataList: result
        }
        console.log(JSON.stringify(data));
        await instance.exit();
    }

}());

输出结果

14995115113865.jpg

  1. QUnit

前台规则录入

那是一个独自的 Web
系统,系统关键用于收集用户录入的页面音信、页面对应的条条框框、浮现错误音讯。通过调用后端页面抓取服务来完成页面检测的任务,系统可以创造三种类型的检测页面:常规监控、高级督察、可用性监控

可以捕捉的web页面

模块完毕

获取具有章节fetchAllChapters.js

const phantom = require('phantom');
const program = require('commander');
/*
  命令行参数帮助工具
  设置 option b 代表 book ,[book]表示该参数可以通过program访问,这个参数表示书本编号
  命令 eg:
  node fetchAllChapters.js -b 5443  
*/
program
    .version('0.1.0')
    .option('-b, --book [book]', 'book number')
    .parse(process.argv);

//缺少书本参数直接退出
if (!program.book) {
    return
}
// example "5443",获取书本编号
const bookNumber = program.book
    //访问的url
const url = encodeURI(`http://www.qu.la/book/${bookNumber}/`);
//设置用户代理头
const userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36`
try {
    //提供async环境
    (async function() {
        //创建实例
        const instance = await phantom.create()
            //创建页面容器
        const page = await instance.createPage()
            //设置
        page.setting("userAgent", userAgent)
            //判断是否访问成功
        const status = await page.open(url),
            code = 1;
        if (status !== 'success') {
            //访问失败修改状态码
            code = -1;
        } else {
            //获取当前时间
            var start = Date.now();
            var result = await page.evaluate(function() {
                var count = 1;
                return $('#list dl dd').map(function() {
                    return ({
                        index: count++,
                        title: $(this).find('a').html(),
                        link: url + ($(this).find('a').attr('href')).substring(($(this).find('a').attr('href')).lastIndexOf("/")),
                    })
                }).toArray()
            })
            let data = {
                code: code,
                bookNumber: "5443",
                url: url,
                time: Date.now() - start,
                dataList: result
            }
            console.log(JSON.stringify(data));
        }
        //退出实例
        await instance.exit();
    })()
} catch (e) {
    console.log(e)
}

输出结果

14995146067785.jpg

在得到具有章节之后,大家要求得到具有章节的内容了

fetchChapter

const phantom = require('phantom');
const mkdirp = require('mkdirp')
const program = require('commander');
const fs = require('async-file')
const path = require('path')
    //设置用户代理
const userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36`
    /*
    命令行参数
    p -替换原文本中的换行空格
    f -保存为文件
    t 自定义输出路径
    u 抓取单章的url
    */
program
    .version('0.1.0')
    .option('-p, --puer', 'puerMode')
    .option('-f, --file', 'save2File')
    .option('-t, --path [path]', 'outPutPath')
    .option('-u, --url [url]', 'url')
    .parse(process.argv);
if (!program.url) {
    return;

}
const URL = program.url;
const DEFAULT_PATH = '/book/default/';

/*
替换br和&nbsp标签
*/
function puer(str) {
    if (!str) {
        return
    }
    str = str.replace(/<br\s*\/?>/gi, "\r\n");
    str = str.replace(/&nbsp;/g, " ")
    return str
}
/*
test url 
node fetchChapter.js -u http://www.qu.la/book/5443/3179374.html -f -p
*/

(async function() {
    //创建实例
    const instance = await phantom.create()
        //创建页面容器
    const page = await instance.createPage()
    page.setting("userAgent", userAgent)
    const status = await page.open(URL),
        code = 1;
    if (status !== 'success') {
        code = -1;
        return;
    } else {
        // await page.includeJs("https://cdn.bootcss.com/jquery/1.12.4/jquery.js")
        // await page.render('germy.png');
        var start = Date.now();
        var result = await page.evaluate(function() {
            //移除一些无关内容(等于直接在结果网页上的dom上进行操作)
            //请注意这里如果调用console.log()是无效的!
            $("#content a:last-child").remove()
            $("#content script:last-child").remove()
            $("#content div:last-child").remove()
            $("#content script:last-child").remove()
            return ({
                title: $("h1").html(),
                content: $("#content").html()
            });
        })
        if (result.title == '' || result.content == '') {
            //内容为空捕获失败
            console.log(JSON.stringify({
                code: -1
            }))
            return
        } else {
            //判断参数进一步处理
            if (program.puer) {
                var context = puer(result.content)
            }
            //文件模式处理后进行保存到文件.返回文件路径
            if (program.file) {

                let path = ""
                if (program.path) {
                    //自定义路径
                } else {
                    path = DEFAULT_PATH;
                    //避免文件夹不存在,__dirname指向的是文件所在路径
                    mkdirp(__dirname + path, (err) => {
                        if (err) {
                            console.log(err);
                        }
                    });
                    //拼接出文件输出的路径
                    path += result.title + ".txt";
                    await fs.writeFile(__dirname + path, context)
                        // return;
                        //输出文件名
                    console.log(JSON.stringify({
                        code: 1,
                        filePath: path
                    }))
                }
            } else {
                console.log(JSON.stringify({
                    code: 1,
                    content: result
                }));
            }

        }
    }
    //exit
    await instance.exit();
})()

澳门葡京 5

好端端监控

录入一个页面地址,和若干检测规则。注意那里的检测规则,大家把常用的片段检测点抽象成了一条看似测试用例的言语。每条规则用来同盟页面上的一个
DOM 元素,用 DOM
元素的性质来和预期做合作,若是协作战败系统就会发出一条错误音讯,后续交由报警系统处理

很是类型 一般有这么二种:长度、文本、HTML、属性

处理器
类似编程语言中的操作符:大于、大于等于、小于、小于等于、等于、正则

那般做的处就是,录入规则的人借使了然一些 DOM
选拔器的学识就足以上手操作了,在大家之中日常是交由测试工程师统一已毕规则的录入

澳门葡京 6

3.互连网监督 品质分析

拓展

 await page.includeJs("https://cdn.bootcss.com/jquery/1.12.4/jquery.js")
 //可以导入其他js lib
 await page.render('germy.png');
 //渲染当前页面为图片输出

在此间说一下为啥可以一贯利用jquery,以百度为例子

14995249028718.jpg

因为眼下页面加载的时候加载了jquery 那几个lib,所以那边就可以直接使用了

QUnit 是个效用强大又易于使用的 JavaScript 单元测试框架。jQuery、jQuery
UI 和 jQuey Mobile 项目都施用这几个框架,它能测试普通的 JavaScript 代码。

高级督察

首要用来提供高级页面测试的效果,一般由有经验的工程师来写作测试用例。那一个测试用例写起来会有一部分上学开支,可是足以如法炮制Web 页面操作,如:点击、鼠标移动等事件就此成就规范捕捉页面新闻

澳门葡京 7

督察page
loading辅助出口HAR标准文件,匡助拔取YSlow和Jenkins举办机动的品质分析。

结缘使用

taskHandler

const exec = require('child_process').exec;
const execAsync = require('async-child-process').execAsync;
const delayAsync = require('./asyncFetch').delayAsync;
const program = require('commander');
let cmd;
/*
s 是章节开始(下标是0,所以需要手动减一,第一章就是 0)
e 是结束章节数
l 是并发数
m 模式
b 书的编号
test command:
node taskHandler.js -s 0 -e 10 -l 3 -b 5443
*/
program
    .version('0.1.0')
    .option('-s, --start [start]', 'start chapter', 0)
    .option('-e, --end [end]', 'end chapter')
    .option('-l, --limit [limit]', 'limit async', 3)
    .option('-m, --mode [mode]', 'Add bbq sauce', 2)
    .option('-b, --book [book]', 'book number')
    .parse(process.argv);
/*
 第一步获取章节连接,第二部获取章节内容并进行输出
 输出方式一 输出到数据库.(未实现)
 输出方式二 文件输出(在关注react-pdf,希望支持pdf输出)
*/
if (!program.book) {
    return
} else {
    cmd = `node fetchAllChapters.js -b ${program.book}`;
}
if (!program.start || !program.end) {
    console.log("must input with start-chapter and end-chapter ")
    return;
}

//
(async function() {

    const {
        stdout
        //调取子进程 执行cmd
    } = await execAsync(cmd, {
        //default value of maxBuffer is 200KB.
        maxBuffer: 1024 * 500
    });
    let data = JSON.parse(stdout),
        start = program.start,
        end = program.end,
        limit = program.limit,
        dataList = data['dataList'],
        fetchResult = null;
        //use to debug 
        // let dataList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        if (!dataList || data.length <= 0) {
            return
        }



        // console.log(dataList)
        //分发任务 每10s调取一次并发抓取10条记录 
        //截取需要的章节数
        /*根据章节,章节是一开始,默认无序章*/
        //dataList, start, end, limit
        //下面是把要抓取的内容放置到delayAsync中,后文讲述delayAsync
    try {
        fetchResult = await delayAsync(dataList, parseInt(start), parseInt(end), parseInt(limit));
    } catch (e) {
        console.log(e)
    }
})()

此地是将四个模块组合起来,先抓取所有章节数再拓展处理

此地运用async-child-process调起子进度,然后径直拿走输出在控制马普托的数据作为出口结果,由于async-child-process默许控制台出口的最大字节流是5kb所以要调整最大字节流的限量,不然会报错;

  1. Sinon

可用性监控

可用性监控侧重于对页面的可访问性、内容科学等相比较 严重的难题
做即时监控。常常那类页面我们只必要在先后里面启一个 Worker
不断的去得到页面 HTML 就可以对结果进行检测匹配了,所以我们挑选了 NodeJS
来做异步的页面抓取队列,高效便捷的达成那种网络密集型任务

澳门葡京 8

  1. 爬虫

  2. 运行自动化测试QA 

整合async 与计时器完成延迟并发加载

此地先要说一下async.js其一库提供了好多控制并发的主意,关于async的demo能够看一下唐大大的async
demo,里面有那多少个async
method 的应用 

而大家在那里运用的是 async.mapLimit()

/*
mapLimit(coll, limit, iteratee, callbackopt)
params coll 是数据集合
       limit 并发数量
       iteratee 迭代器fun(fun 提供item 和callback,通过ca)
       callcackopt collection执行完毕或者是错误出现执行的回调函数

A callback which is called when all iteratee 
functions have finished, or an error occurs.
 Results is an array of the transformed items 
 from the coll. Invoked with (err, results).      
*/
//
var arr = [{name:'Jack', delay:200}, 
{name:'Mike', delay: 100},
 {name:'Freewind', delay:300}, 
 {name:'Test', delay: 50}];
async.mapLimit(arr,2, function(item, callback) {
    log('1.5 enter: ' + item.name);
    setTimeout(function() {
        log('1.5 handle: ' + item.name);
        if(item.name==='Jack') callback('myerr');
        else callback(null, item.name+'!!!');
    }, item.delay);
}, function(err, results) {
    log('1.5 err: ', err);
    log('1.5 results: ', results);
});
/*
20.675> 1.5 enter: Jack
20.682> 1.5 enter: Mike
20.786> 1.5 handle: Mike
20.787> 1.5 enter: Freewind
20.887> 1.5 handle: Jack
20.887> 1.5 err: myerr
20.887> 1.5 results: [ undefined, 'Mike!!!' ]
21.091> 1.5 handle: Freewind
*/

//在看另外一段

const async = require('async');
const moment = require('moment');
var arr = [{
    name: 'Jack',
    delay: 200
}, {
    name: 'Mike',
    delay: 100
}, {
    name: 'Freewind',
    delay: 300
}, {
    name: 'Test',
    delay: 50
}];
var log = function(msg, obj) {
    //对log进行了封装。主要是增加了秒钟的输出,通过秒数的差值方便大家对async的理解。
    process.stdout.write(moment().format('ss.SSS') + '> ');
    if (obj !== undefined) {
        process.stdout.write(msg);
        console.log(obj);
    } else {
        console.log(msg);
    }
}
async.mapLimit(arr, 2, function(item, callback) {
    log('1.5 enter: ' + item.name);
    setTimeout(function() {
        log('1.5 handle: ' + item.name);
        // if (item.name === 'Jack') callback('myerr');
        callback(null, item.name + '!!!');
    }, item.delay);
}, function(err, results) {
    log('1.5 err: ', err);
    log('1.5 results: ', results);
});

/*
18.951> 1.5 enter: Jack
18.958> 1.5 enter: Mike
19.062> 1.5 handle: Mike
19.063> 1.5 enter: Freewind
19.162> 1.5 handle: Jack
19.162> 1.5 enter: Test
19.217> 1.5 handle: Test
19.367> 1.5 handle: Freewind
19.367> 1.5 err: null
19.369> 1.5 results: [ 'Jack!!!', 'Mike!!!', 'Freewind!!!', 'Test!!!' ]
*/

更直观的收看callcackopt的调用是在error或者全部完了后调用的,result里放着的是每趟callback(null,result)调用的结果以数组的格局储存,注意若是某个函数没有利用该回调,在结果里体现就是undefined
关于截止后仍输出,就是异步机制的题目(或者说是cpu调度难题?),已经调起了控制台的出口后
callcackopt才调用

粗粗领会async.mapLimit的施用后来看一下当下自己的兑现和存在的标题

const async = require('async')
const execAsync = require('async-child-process').execAsync;
/*实现并发抓取的函数*/
var asyncFetch = function(data, number, method) {
        return new Promise(function(resolve, reject) {
            if (!data || data.length <= 0) {
                reject("data not exist")
            }
            let result = [];
            async.mapLimit(data, number, async(data, callback) => {
                //需要设置延时不然ip会被封掉
                let cmd = `node fetchChapter.js  -u ${data.link} -f -p`,
                    json,
                    //获取一个内容就输出一个
                    {
                        stdout
                    } = await execAsync(cmd, {
                        //default value of maxBuffer is 200KB.
                        maxBuffer: 1024 * 500
                    });
                /*将内容保存到json中*/
                json = JSON.parse(stdout);
                //保存index
                json.index = data.index;
                /*
                由于设置成了async,出现了多次触发err的情况,callback 不能正常工作,
                手动推入result中,但是这样顺序是不确定的,有待解决这个问题
                */
                result.push(json);
                callback(null, json) //not work 
            }, function(err) {
                //回调函数在全部都执行完以后执行
                if (err) {
                    reject(err)
                }
                resolve(result)
            })
        })
    }
    /*实现延时加载的函数*/
var delayAsync = function(dataList, start, end, limit) {
    return new Promise(function(resolve, reject) {
        var result = [],
            counter = 0,
            checkTimer,
            checkTimeOut,
            fetchTimers = [],
            count = Math.ceil((end - start) / limit),
            remain = start - end,
            i = 0;
        if (dataList.length <= 0) {
            //数据长度为空就返回
            reject("error")
            return;
        }
        //打印一下输入情况
        console.log(dataList)
        try {
            /*章数的开始和结束*/
            console.log(`从${start}到 ${end}`)
            let startIndex = start,
                endIndex;
            while (startIndex != end) {
                /*
                需要注意的是当剩余的任务不足以达到并发数的时候
                要保证任务分割不能出界
                */
                if (startIndex + limit < end) {
                    endIndex = startIndex + limit;
                } else {
                    //截取出界
                    endIndex = end;
                }
                /*分割任务*/
                chapter = dataList.slice(startIndex, endIndex);
                //通过闭包实现IIFE保存当时抓取的情况,不使用闭包绑定的数据则是运行之后的值
                (function(startIndex, endIndex, chapter) {
                    //通过tempTimer 保存下来
                    let tempTimer = setTimeout(async function() {
                        //获得此次任务开始执行的时间
                        let startTime = new Date(),
                            time, chapterResult = [];
                        //进行并发捕获执行命令
                        try {
                            chapterResult = await asyncFetch(chapter, limit);
                        } catch (e) {
                            // console.log(e)
                        }
                        result = result.concat(chapterResult)
                            //用于判断任务标记 
                        counter++;
                        time = new Date() - startTime;
                        console.log(`完成抓取 ${startIndex} 到 ${endIndex} 计数器是${counter} 时间是${time}`)
                    }, i * 1000);
                    fetchTimers.push(tempTimer);

                })(startIndex, endIndex, chapter)
                i++; //控制延时
                //推进任务进行
                startIndex = endIndex;
            }
        } catch (e) {
            reject(e)
        }
        /*定时判断任务是否完成*/
        checkTimer = setInterval(function() {
            console.log(`counter is ${counter} count is ${count}`)
            if (counter == count) {
                //清除定时器
                clearTimeout(checkTimeOut);
                //清除定时器
                clearInterval(checkTimer);
                resolve(result)
            }
        }, 1000);
        //or use promise all ?
        //30s计时器判断超时,超时时间暂做距离
        checkTimeOut = setTimeout(function() {
            //超时清除所有定时器
            for (let i = 0; i < fetchTimers.length; i++) {
                clearTimeout(fetchTimers[i]);
            }
            //清除定时判断
            clearInterval(checkTimer);
            console.log("timout")
            reject(result)
        }, 30000);
    })
}

module.exports = {
    asyncFetch: asyncFetch,
    delayAsync: delayAsync,
}

眼前在async中留存难题,callback函数无法健康干活,所以每回都是手动将结果推入结果集,导致结果集的相继不可能和原数据顺序对应,
但是async官方文档中

The callback must be called exactly once, ideally on a later tick of
the JavaScript event loop.

起码要调用两次callback? 不过

在延时出现中考虑用await Promise.all[] 取代定时器判断义务是或不是截至

输出结果

14995701558979.jpg

Sinon.JS 为 JavaScript 提供了独立的 spies、stubs 和 mocks
[翻译注:Spy、Stub 和 Mock 都是测试专用名词,Stub 常被翻译为桩,spies
是 Spy
的复数情势,是一种可以监视措施、调用和参数的技术]。它不借助于任何东西,可以匹配其余单元测试框架工作。

主动错误反馈

 

储存到mongodb

此间运用的数据库驱动模块是
mongolass

  1. Karma

页面脚本执行错误监控

页面引入一段监控脚本来收集页面产成 error
事件再次回到的错误音讯,自动上报给后端服务,在系统内部可以集中所有报错新闻,以及对应的客户端浏览器版本、操作系统、IP
地址等

phantomjs的生态圈

先是步配置mongolass并累加模型

const Mongolass = require('mongolass');
const moment = require('moment');
const objectIdToTimestamp = require('objectid-to-timestamp');
const mongolass = new Mongolass();
//储存的库的url 
mongolass.connect('mongodb://localhost:27017/novel');
// 根据 id 生成创建时间 created_at
mongolass.plugin('addCreatedAt', {
  afterFind: function(results) {
    results.forEach(function(item) {
      item.created_at = moment(objectIdToTimestamp(item._id)).format('YYYY-MM-DD HH:mm');
    });
    return results;
  },
  afterFindOne: function(result) {
    if (result) {
      result.created_at = moment(objectIdToTimestamp(result._id)).format('YYYY-MM-DD HH:mm');
    }
    return result;
  }
});
/*
  下面模型的意思是
  Book表
  字段      属性
  bookNum  string
  url      stirng
  chapters 对象数组 - 对象的属性是index - number ...类推
*/
exports.Book = mongolass.model('Book', {
  bookNum: {
    type: 'string'
  },
  url: {
    type: 'string'
  },
  chapters: [{
    index: {
      type: "number"
    },
    link: {
      type: "string"
    },
    title: {
      type: "string"
    }
  }]
});
//书模型
exports.Book.index({
  bookNum: 1
}, {
  unique: true
}).exec(); // 根据书本编号找到书本的章节,书编号全局唯一

/*
  下面模型的意思是
  Chapter表
  字段      属性
  bookNum  string
  start    number
  end      number
  chapters 对象数组 - 对象的属性是code - number ...类推
*/
exports.Chapter = mongolass.model('Chapter', {
  bookNum: {
    type: 'string'
  },
  start: {
    type: 'number'
  },
  end: {
    type: 'number'
  },
  chapters: [{
    code: {
      type: 'number'
    },
    filePath: {
      type: 'string'
    },
    index: {
      type: 'number'
    }
  }]
});

//抓取一次章节的模型
exports.Chapter.index({
  bookNum: 1
}, {
  unique: true
}).exec(); // 根据书本编号找到书本的章节,用户名全局唯一

Karma
是指向连通浏览器的一个框架毫不相关测试运行器。每一个测试结果对应每个浏览器,它的测试和出示都是透过命令行暴露给开发者的,那样他们就足以观察浏览器测试的经过或败北。

页面主动申报

其一意义需求相应的前端工程师在代码中调用错误上报
API,来主动提交错误新闻。主要采用的场景有,页面异步服务延时无响应、模块降级兜底主动打招呼等。监控脚本提供多少个大约的
API 来形成那项职责

// error 方法调用后立时上报错误音信并发出邮件、短信文告errorTracker.error(‘错误描述’) // info
方法调用后立马反馈音信,并在单位时间内仅爆发一条邮件、短信通知errorTracker.info(‘音信描述’) // log
方法调用后由报错检测是不是达到设置阀值,最后认可是不是报错
errorTracker.log(‘日志新闻’)

1
2
3
4
5
6
7
// error 方法调用后立即上报错误信息并发出邮件、短信通知
errorTracker.error(‘错误描述’)
// info 方法调用后立即上报信息,并在单位时间内仅产生一条邮件、短信通知
errorTracker.info(‘信息描述’)
// log 方法调用后由报错检测是否达到设置阀值,最终确认是否报错
errorTracker.log(‘日志信息’)
 

1. CasperJS:一个开源的导航脚本处理和高级测试工具

累加模型

Book

const Book = require('../lib/mongo').Book;

module.exports = {
  // 保存章节内容
  create: (book) => {
    return Book.create(book).exec();
  },
  //通过书编号获取记录
  getBookByBookNum: (bookNum) => {
    return Book
      .findOne({
        bookNum: bookNum
      })
    .addCreatedAt()
      .exec();
  },
  //通过编号更新书数据
  updateBookByBookNum: (bookNum, book) => {
    return Book.update({
      bookNum: bookNum,
    }, {
      $set: book
    }).exec();
  },
};

Chapter

const Chapter = require('../lib/mongo').Chapter;

module.exports = {
  // 保存章节内容
  create: (chapter) => {
    return Chapter.create(chapter).exec();
  },
  //通过书编号获取记录
  getChapterByBookNum: (bookNum) => {
    return Chapter
      .find({
        bookNum: bookNum
      })
      .addCreatedAt()
      .exec();
  },
  //通过抓取结果序号获取记录
  getChapterById: (id) => {
    return Chapter
      .findOne({
        _id: id
      })
      .addCreatedAt()
      .exec();
  },
  updateChapterByBookNum: (id, chapter) => {
    return Chapter.update({
      _id: id
    }, {
      $set: chapter
    }).exec();
  },
};

测试(暂未选择断言库进行专业的测试)

const BookModel = require('../model/Books.js');
const ChapterModel = require('../model/Chapters.js');



var testStoreBook = async() => {
    //模拟数据
    let data = {
            bookNum: "4445",
            url: "www.google123.com",
            chapters: [{
                index: 5,
                link: "333",
                title: "123132"
            }, {
                index: 6,
                link: "333",
                title: "123132"
            }, {
                index: 7,
                link: "333",
                title: "123132"
            }]
        },
        bookNum = "4445"

    try {
        var query = await BookModel.getBookByBookNum(bookNum);
        // var result = await BookModel.create(data);
    } catch (e) {
        console.log(e)
    }
    console.log(result.result.ok)
        // process.exit()
}
var testStoreChapters = async() => {
        //模拟数据
        let data = {
                bookNum: "4445",
                start: 0,
                end: 10,
                chapters: [{
                    index: 5,
                    code: 1,
                    filePath: "123132"
                }, {
                    index: 6,
                    code: 1,
                    filePath: "123132"
                }, {
                    index: 7,
                    code: 1,
                    filePath: "123132"
                }]
            },
            bookNum = "4445"

        try {
            // var result = await ChapterModel.updateChapterByBookNum(bookNum, data);
            var result = await ChapterModel.getChapterByBookNum(bookNum);
            console.log(result)
        } catch (e) {
            console.log(e)
        }
    // console.log(result.result.ok)
            // process.exit()
    }
    (async function() {
        try {
            // await testStoreChapters()
            // var query = await testStoreBook()
            var query = await testStoreChapters()
        } catch (e) {
            console.log(e.message)
        }
    })()
  1. Selenium

后端页面抓取服务

鉴于京东为数不少页面内容是异步加载的,像首页、单品等系统有不少第三方异步接口调用,使用后端程序抓取到的页面数据是一块的,并无法取到动态的
JavaScript 渲染的内容,所以就必须利用像 PhantomJS 这种能效仿浏览器的工具

正规监控大家利用 PhantomJS
模拟浏览器打开页面进行抓取,然后将督查规则解析成 JavaScript
代码片段执行并募集结果

高等督察我们利用 PhantomJS 打开页面后向页面注入像 jasmine, mocha
等接近的前端 JavaScript
测试框架,然后在页面执行相应的录入测试用例并回到结果

2. Mocha-PhantomJS:JavaScript测试框架Mocha的客户端

结缘mongolass保存抓取数据

积存章节新闻

const BookModel = require('./model/Books.js');
// ...
if (!dataList || data.length <= 0) {
        return
    }
    /*储存数据*/
    let book = {
            bookNum: data.bookNumber,
            url: data.url,
            chapters: dataList,
        },
        result = await BookModel.create(book);
    console.log(result)
//...

输出结果

14996015073753.jpg

存储章节内容

const ChapterModel = require('./model/Chapters.js');

//....
    try {
        fetchResult = await delayAsync(dataList, start, end, limit);
        console.log(fetchResult)
        var chapters = await Chapter.create({
            bookNum: data.bookNumber,
            start: start,
            end: end,
            chapters: fetchResult,
        });
        console.log(chapters)
    } catch (e) {
        console.log(e)
    }

输出结果

image.png

澳门葡京 9

规则队列生成器

规则队列生成器会将征集的规则转化类成新闻队列,然后交由长时频频处理器一遍拍卖

怎么使用类音讯队列的处理格局?

那和 PhantomJS 的性质是紧密的,由很多次履行发现,PhantomJS
并不可以很好地开展并发处理,当并发过多,会促成 CPU 过载,从而造成机器宕机

在本机环境下的虚拟机中举行并发测试,数据并不佳好,极限基本在 ab -n 100
-c 50 左右。
所以为了幸免出现导致的题材,就选取了运用类音信队列来幸免因为并发过高以致的劳动不可用

 

反思

时下感觉总体设计上并不是老大靠边。

书籍的章节可以捕获四回保存在数据库中,输入书本后判断书本是还是不是曾经抓获过章节了

抓获过就从数据库里拿走须求的章节,提供格局检验是或不是有新型章节,

以文件形式储存阅读并不便利,怎么样更有益于的读书

在大方破获的时候仍会被封停,缺乏应对封停的建制

添加phantom proxy
举办代理,那里引出须要写一个抓取代理并测试的劳动来提供代理池

(ps =,=寝室只可以用热点上网 实在网络不顺遂)

Selenium 有一个简便的目的:就是自动化浏览器。它根本用于自动化测试 web
应用程序,不过只是很简短地考虑到了基于互联网的军事管制任务。

类音信队列的落到实处

俺们那里通过调用内部的分布式缓存系统生成类音讯队列,队列的生成其实可以参考数据结构–队列。最大旨的模型就是在缓存中创建一个
KEY ,然后依据队列数据结构的方式展开数据的插入和读取

当然,类新闻队列的中档介质可按照你实际的规格来抉择,你也可以选用本机内存完毕。那恐怕会导致应用和类音讯队列竞争内存

前者页面监控

  1. WebdriverIO

长时持续处理器

长时持续处理器是要效益就是消费规则队列生成器生成的类信息队列

前端页面监控:比如,页面第三方系统数据调用败北,模块加载非凡、用户白屏、数据不得法。那时候就需求以前端DOM体现的角度来分析。并且出现难题,需求运用邮件、短信文告有关人口修复bug。并且触发报警是要有现场快照,以便复现难题。

WebdriverIO
允许用户仅增加几行代码就足以操纵浏览器或挪动应用程序,使测试代码更不难、简洁、易读。集成的
TestRunner 同样允许你以联合的措施调用异步命令,那样你不须要关爱什么处理
Promise
以避免竞态条件。其它,它裁撤了装有的累赘的装置工作,并且会为您管理的 Selenium 会话。

长时持续处理已毕

在长时持续处理器的现实性贯彻中,咱们应用了 JavaScript 的 setInterval
方法来不断取得累音信队列的始末下发给规则转化器,然后转载给负载均衡调度器。之后再对回到的结果举行统一处理,比如邮件或者短信报警

UI测试包含网页元素的不利突显,网页交互之后的要素变化等,人工测试万分头疼,并且UI层面的测试用例也糟糕写。

  1. Nightwatch

API

PhantomJS 服务可以做为公共 API 提要求客户端举办测试须要的拍卖, API 通过
HTTP 方式调用。在 API 的处理上急需提供 HTTP 数据到规则和 PhantomJS
的更换。从而又衍变出了 HTTP 数据到规则转换器

 

澳门葡京 10

PhantomJS 服务

PhantomJS 服务是指将 PhantomJS 结合 HTTP 服务和子进度展开服务化的拍卖

率先、启动 HTTP
服务,然后将长时电脑下发的条条框框举行更为转化,转化后启动子进度,HTTP
服务会监听子进程的处理结果,并在处理落成之后回来

注入JS文件

Nightwatch.js 是一个不难使用的 Node.js,它是为基于浏览器的 app
和网站设计的极限到极点(E2E)的测试方法。它选择强劲的 W3C WebDriver
API
 ,用于在 DOM
元素上执行命令和断言。

报警系统

报警系统大家近期使用的是京东里面团结的会师监督平台
UMP,通过调用平台提供的片段 API 来落到实处报警邮件与短信公告

var webPage = require('webpage');
var page = webPage.create();
page.includeJs('http://code.jquery.com/jquery-1.10.2.min.js', function() {
// 模拟登录
var $testForm = $('form#login');
$testForm.find('input[name="username"]').value('barret');
$testForm.find('input[name="password"]').value('1234');
$testForm.submit();
});
  1. PhantomCSS

什么样依据报警定位到具体页面?

用户通过监督管理连串录入规则后,监控系列会依照 UMP
规则针对用户录入的页面生成 UMP 使用的 key。当长时连连处理器发现
PhantomJS 服务再次回到的结果标示为那么些后,就会选择 key 来举办日志记录

 

PhantomCSS 得到 CasperJS 捕获的屏幕截图,并动用 Resemble.js
将其与规范图进行自查自纠,以测试 RGB 像素差距。 PhantomCSS
然后生成图像差别相比,用于协助你找到原因。

哪一天出发报警?

报警主要分为了短信和邮件报警。邮件报警是在每条非凡之后就会发给指定系统用户。短信则是按照非常次数来开展拍卖的,当十分次数过大,就会下发短信文告

执行JS代码

  1. PhantomFlow

部署

对此系统安排可以分为两大块举办。因为机器资源数量少于,没有将具有片段都独立陈设

规则管理种类以及规则队列生成器和不断处理器整合安插在一台机械上,PhantomJS
服务配置在了其余的机械上。进程管理应用了资深的 NPM 模块 —— PM2

PM2 是一个含有负载均衡成效的 NodeJS 应用的经过管理器。可充裕利用
CPU,并有限协助进度平稳存活

PM2 特性:

  • 内建负载均衡(使用 Node cluster 集群模块)
  • 无缝重启类似 nginx reload
  • 装有 Ubuntu 和 CentOS 的开机启动脚本
  • 控制台检测

唯独在当下布置职责中,并从未运用内建负载均衡的特征,没用经过集群的艺术配置代理。仅使用了后台运行和无缝重启的特性

1 var webPage = require('webpage');
2 var page = webPage.create();
3 page.open('http://www.taobao.com', function(status) {
4 var title = page.evaluate(function() {
5 return document.title;
6 });
7 console.log(title);
8 phantom.exit();
9 });

PhantomFlow 使用决策树提供 UI
测试方案。针对 PhantomJS, CasperJS 和 PhantomCSS 的
NodeJS 包装器—— PhantomFlow
可以流畅地在代码中描述用户流程,同时生成用于可视化的协会化树数据。

总括与展望

事实上大家后日支出的那套监督系统并不复杂,只是合理的应用了一部分现有的技巧框架。抽象出来大家和好需求的片段效益。但却使得的达成了大家的料想成效,并且节省了众多以前须求人肉测试的时刻费用。系统本身还有许多题材在待解决情形,比如报警系统的平整处理与阀值设定,JavaScript
报错的确切过滤机制等,这些难题大家都会逐个缓解,并且将来的前端监控系统会变成一个阳台,主旨服务在后端爬取页面服务,应用端可以有三种格局,比如监控、测试工具等

有些足以穿梭优化点:

  • 监控种类即便在动用范围举行了垂直细分,但是出于机械资源等限制,并不曾举办单独功效的配备。这一点可能会在晚期的应用中举行优化
  • PhantomJS
    服务还须要越发优化,以承载大出现,大处理量。提供稳定的劳务
  • 报警是因为看重于公司内部的 UMP
    系统,所以并不是更加灵巧,后期可以设想自己落成一套报警机制

博客原文同步:https://keelii.github.io

1 赞 1 收藏
评论

 

  1. Percy.io

关于小编:keelii

澳门葡京 11

It’s not who you are underneath,
it’s what you do that defines you
个人主页 ·
我的小说 ·
5 ·
     

澳门葡京 12

测试页面加载速度

 Percy
提供有关视觉变化的迭代及高速反馈,带来了所谓的总是视觉集成。它是由此上边格局贯彻的:运行测试套件,获取
DOM 快照并上传到 Percy 服务,最后在浏览器中渲染之。

var page = require('webpage').create(),
  system = require('system'),
  t, address;

if (system.args.length === 1) {
  console.log('Usage: loadspeed.js <some URL>');
  phantom.exit();
}

t = Date.now();
address = system.args[1];
page.open(address, function(status) {
  if (status !== 'success') {
    console.log('FAIL to load the address');
  } else {
    t = Date.now() - t;
    console.log('Loading ' + system.args[1]);
    console.log('Loading time ' + t + ' msec');
  }
  phantom.exit();
});

phantomjs loadspeed.js http://www.baidu.com 


输出 Loading http://www.baidu.com Loading time 3802msc

屏幕截图

var page = require('webpage').create();
page.open('http://github.com/', function() {
  page.render('github.png');
  phantom.exit();
});

英文原文:12 must-have code testing
tools 译文:

 

设置页面背景颜色

page.evaluate(function() {
  document.body.bgColor = 'white';
});

 确保在render之前

无界面的测试

PhantomJS itself is not a test framework, it is only used to launch the
tests via a suitable test runner.

例如Mocha Jasmine WebDriver 

和CI系统持续集成
例如Jenkins或者TeamCity,Travis CI已经内置了对PhantomJS的支持。

最好的实践
和测试框架CasperJS集成。

 

 相关连接

官网: www.phantomjs.org

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website