本文首发于个人博客,转载请保留出处http://jsorz.cn/blog/2016/11/node-action-in-website-detection。html(这里是一篇拖了将近一年的文章。。。)去年这个时候接手了一个累死人的项目。总体目标是:1.为用户网站提供安全监控服务。用户在我们的管理系统中添加站点并勾选需要检测的项目;2、然后后台爬虫系统完成用户站点链接的存储,定时对站点内的链接进行各种检测任务;3、然后前端系统向用户下发网站监控报告。中间一步,最重要的爬虫系统,第一次学习使用Nodejs。节点开始安装nvmcurl-o-https://raw.githubusercontent.com/creationix/nvm/v0.25.2/install.sh|bashnode0.12.xnvminstall0.12cnpmmirrornpminstall--registry=http://r.cnpmjs.org-gcnpmdebugtoolcnpminstall-gnode-inspectornode-debugyourApp.js教程Node.js包教学不包含中文APIdocumentSuperagent有更多的语法配置项。如果superagent是$.post(),那么request就是$.ajax()cheerio用于DOM解析,提供类似jqueryselector的接口。它是GithubPromise中异步进程控制Q的一个实现。承诺不是万灵药。转换复杂且动态确定的执行链很烦人。异步有很多喜欢。有人说它有毒。我觉得用eventproxy@朴黎的基于事件的流程控制很简单。我觉得兼容promise或者async摘要里说了,主程序设计非常好用。本项目的目标分为3个步骤:添加检测任务=>爬虫执行任务=>前端报表展示。但是在实际实现中,需要对爬虫进行细分,最后会有6步流程。1.添加监控站点在web端添加站点任务,输入客户概要信息和监控站点列表,即可更改检测类别周期和具体检测项目等配置。“可用性”检测主要是网站的一些基本信息,通常可以在首页的http头中找到。但是“内容检测”需要检测网站中的所有页面,所以会有“检测深度”和“最大页面数”的配置。巨大的压力。此外,还有一个“安全检测”模块。具体检测项目包括:ActiveX木马、iframe木马、js木马、WebShell后门、暗链接、后台地址检测。由于本人对安全了解太少,在本系统中的实现也很简单,就不多说了。..2.站点入库由于上面的“内容检测”和“安全检测”需要检测网站首页下的所有其他页面,所以我们需要维护一张site表和site_link表,记录该网站的域名和ip信息网站,并保存每个页面的链接信息,包括页面相对于首页的深度级别,页面链接是否来自同源、同域或外部链接,以及发现时间和状态信息页面的。另外需要注意的是,从首页开始抓取链接,再抓取其后继链接到链接,整个过程要采用“广度优先”的策略。“检测深度”和“最大页数”此时也有限制。如果当前页面深度为N,且N+1>检测深度,则该页面将不再推送爬行队列中的后续链接。3、定时生成检测任务配置站点任务并将站点链接入库后,可以按照上面配置的检测周期定时生成任务。例如,当内容检测期到来时,需要提取站点下所有匹配的页面链接,针对每个页面的每个检测项生成检测任务。需要注意的是,在生成任务时,需要按照页面链接的顺序遍历,而不是先遍历检测项。这样做的好处是当执行特定的爬虫时,队列中相同url的检测任务会连续,这样爬虫就可以组合起来进行爬取。同一个url只需要抓取一次就可以给不同的检测任务执行。4.爬虫执行检测任务。如果第3步的生成任务是“生产者”,那么现在就轮到“消费者”了。爬虫系统必须支持并发,多机+多线程。在一台机器上,一次从任务队列中取出100个任务。如步骤3所述,检测任务按照同一个url连续排列,那么这100个检测任务可以抽取20个任务组(举例),每个任务组只能抓取一个页面,然后将内容传递给每个检测执行项目。每个检测项的执行结果以日志的形式存储。如果爬虫的机器不是那种“树莓派”微型机器,可以先将日志以文件的形式保存在本地,为爬虫预留带宽。或者将每个检测项的结果日志发送到指定的地方,这样会占用较多的带宽,需要专门的日志服务器。5、定时计算日志爬虫任务执行过程非常连续,又非常离散。连续是因为完成一项任务后,将完成下一项任务。Discrete表示日志是离散的,一页一个检测项,单页一个。一次的执行日志意义不大,只有结合整个站点、时间段、检测类别等维度才有价值。从前面的步骤可以看出,站点链接和周期性生成任务的存储会有一定的“顺序”控制,全部存储完成后才会进入下一步。爬虫系统是非常离散的,可能会分布到多台机器上。只要任务队列里有东西,就会取出来执行,不会区分任务来自哪个站点或类别。因此,如果日志分布在每台爬虫机器上,就需要一个定时脚本将它们汇总计算后传回数据中心。而如果有统一的日志服务器,还需要将原始日志计算成有效数据。6.前端报表查询前端系统直接查询数据中心,可以得到站点级和检测类别级的报表。之前的“生产者”在生成检测任务时,会在数据库中标记一些任务模块的时间信息,前端系统也可以查看一些调度状态信息。架构图整个系统分为前端系统、任务生产者、爬虫系统、日志计算同步过程四个部分。架构图如下。您可以看到箭头代表数据流的方向。爬虫的调度和执行是典型的“生产者-消费者”模型,只有一个生产者和N个消费者爬虫进程。所谓检测任务队列,其实就是数据库中的一张表。多个爬虫进程或多个爬虫机器共享这张表。只要从表头开始读,读完就需要重新设置状态位。防止其他进程重复读取。当任务执行失败时,会将检测任务的记录移动到表尾,并设置失败次数的字段。在本文的第一节中,爬虫实现思路的技术框架中列举了一些库。在页面爬取方面,使用superagent获取页面内容,使用cheerio进行文档分析。对于一些不需要解析内容的爬取任务(比如查询某个页面的头部信息,或者查看某个页面是否有200状态),使用request发送请求。对于爬虫系统中最关键的异步流控,我在实现上尝试了多种风格,在数据库读写层面使用promise风格,在检测项内部使用async,在爬虫实例中使用eventproxy进行流式处理控制。目录结构base/继承和通用相关的conf/各种配置项(检测项的配置、规则的配置)常量/各种常量的定义和配置数据库/数据库配置和连接池模型/数据库表对应的jsonschemaadao/Dao对应模型CRUD封装util/通用数据helperfactory/工厂封装类,统一任务构建流程模块/具体检测项(爬虫承载的具体任务实现)basic/基础信息可用性检测content/内容检测大类secure/安全检测categorycommon/站点存储、链接存储、爬取页面内容也被抽象成一个通用的taskcrawler/爬虫对象和pool管理(1个爬虫实例只负责1个页面请求)scheduler/监控调度过程(每轮执行一次偶尔)runner.js用于执行检测任务(任务消费者)siteRunner.js是runnerapp.js的主程序,用于“站点链接存储”,用于启动runnerappSite.js主程序之一,用于启动SiteRunnerappPath.js,启动低频高请求检测任务(详见下文地址类型检测类)。从上面的结构可以看出爬虫的实现过程。程序运行时的调用流程为:app.js=>runner.js=>爬虫=>具体检测项。app.js其中,app.js只是一个程序入口,为runner构造了一些参数。代表参数有:runner在每轮执行中取出的task数量(即一个consumer每次消费的数量),和轮次执行之间的间隔(因为执行时会有N次请求一轮任务,如果没有间隔限制,同时等待请求响应的线程过多,就会爆炸)。runner.jsrunner.js首先提供了一个支持时间间隔的循环执行接口,这里使用async库实现。RunnerBase.prototype={开始:函数(){varthat=this;那._count=0;async.whilst(function(){if(that.loopCount){returnthat._count
