【亚洲必赢官网】代码测试必备工具,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是一个依据Web基特的服务器端JavaScript API,它依据BSD开源协议宣布。PhantomJS无需浏览器的协助即可落成对Web的支撑,且原生协理种种Web标准,如DOM
处理、JavaScript、CSS选取器、JSON、Canvas和可缩放矢量图形SVG。PhantomJS主若是通过JavaScript和CoffeeScript控制WebKit的CSS选用器、可缩放矢量图形SVG和HTTP互联网等逐个模块。

  1. Jasmine

内需监控种类缓解的标题

页面常常出现以下难点时索要利用邮件、短信通知相关人口修复难点

  • 状态码再次来到错误(50x, 40x)不能够打开
  • 【亚洲必赢官网】代码测试必备工具,phantomjs制作nodejs的随笔爬虫。模块加载失利
  • 页面乱码
  • 数码科学

接触报警时要有现场快照,以便复现难题

 

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目录下
后文也会尤其验证使用格局。

亚洲必赢官网 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
来模拟浏览器的操作。它使用 QtWeb基特 作为它的浏览器宗旨,使用 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

在选拔规模上进行的垂直细分可以对应用做分布式计划,提升处理能力。中期也有利于做品质优化、系统改造扩充等

可以和单元测试框架如Jasmine、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

网站地图xml地图