前言熟悉我的朋友可能知道我没写过热点。为什么不?是因为我不注意热点吗?其实并不是。我对一些事情还是很关心的,确实有很多想法和看法。但我一直奉行一个原则,那就是:以活力为知足。本文介绍的内容来自笔者之前负责研发的爬虫管理平台。专门抽象出一个相对独立的功能模块来讲解如何使用nodejs开发自己的爬虫平台。文章涵盖了很多知识点,包括nodejs,作者会用尽可能通俗易懂的语言向大家介绍爬虫框架、父子进程及其通信、react和umi。您将获得Apify框架的介绍和基本用法如何创建父子进程和父子进程通信使用javascript手动实现控制最大并发爬虫数拦截整个网页图片使用nodejs实现方案使用umi3+antd4.0搭建爬虫前端界面平台预览上图是我们要实现的爬虫平台,我们可以输入指定的url来抓取网站下的数据,生成一个第三方库和模块整个网页的快照。抓拍完成后,我们就可以下载数据和图片了。页面右侧是用户抓取的记录,方便二次使用或备份。在开始本文之前,我们需要先了解一下爬虫的一些应用。我们一般所知道的爬虫大多用于爬取网页数据、抓取请求信息、网页截图等,如下图所示:当然,爬虫的应用远不止于此,我们还可以使用爬虫库进行自动化测试、服务器端渲染、自动表单提交、测试Google扩展、性能诊断等。任何语言实现的爬虫框架的原理往往是相似的。接下来笔者将介绍基于nodejs的爬虫框架Apify及其使用方法,并通过一个实用的案例方便大家快速上手爬虫开发。Apify框架介绍和基本使用apify是一个可扩展的JavaScript网络爬虫库。能够使用无头Chrome和Puppeteer开发数据提取和Web自动化作业。它提供用于管理和自动缩放无头Chrome/Puppeteer实例池的工具,支持维护目标URL请求队列,并可以将抓取的结果存储在本地文件系统或云端。我们安装使用起来非常简单,官网上也有很多例子可以参考。具体安装和使用步骤如下:安装npminstallapify--save用Apify开始第一种情况constApify=require('apify');Apify.main(async()=>{constrequestQueue=awaitApify.openRequestQueue();awaitrequestQueue.addRequest({url:'https://www.iana.org/'});constpseudoUrls=[newApify.PseudoUrl('https://www.iana.org/[.*]')];constcrawler=newApify.PuppeteerCrawler({requestQueue,handlePageFunction:async({request,page})=>{consttitle=awaitpage.title();console.log(`Titleof${request.url}:${title}`);awaitApify.utils.enqueueLinks({page,selector:'a',pseudoUrls,requestQueue,});},maxRequestsPerCrawl:100,maxConcurrency:10,});awaitcrawler.run();});使用node执行后,可能会出现如下界面:程序会自动打开浏览器,打开符合条件的url页面。我们还可以使用它提供的cli工具实现更方便的爬虫服务管理等功能,有兴趣的朋友可以试试看。apify提供了许多有用的API供开发者使用,如果想实现更复杂的能力,可以研究一下。下图是官网api的截图:我要实现的爬虫主要是使用了Apify集成的Puppeteer能力。如果对Puppeteer不熟悉,可以去官网了解一下。列出项目使用的技术框架的文档地址。如何创建父子进程以及父子进程通信如果我们要实现一个爬虫平台,需要考虑的一个关键问题是何时以及如何执行爬虫任务。因为爬取网页和截图需要在所有网页加载完成后进行处理,以保证数据的完整性,所以我们可以认为这是一个耗时的工作。当我们使用nodejs作为后台服务器时,由于nodejs本身是单线程的,当爬取请求传递给nodejs时,nodejs必须等待这个“耗时任务”完成才能处理其他请求,这会导致其他页面上的请求等待任务完成再进行,所以为了更好的用户体验和流畅的响应,我们不考虑多进程处理。好在nodejs设计支持子进程,我们可以将爬虫等耗时任务放到子进程中处理,子进程处理完成后通知主进程。整个过程如下图所示:nodejs有3种创建子进程的方式,这里我们使用fork来处理,具体实现如下://child.jsfunctioncomputedTotal(arr,cb){//耗时计算任务}//与主进程通信//监听主进程信号process.on('message',(msg)=>{computedTotal(bigDataArr,(flag)=>{//发送一个完成信号给主进程process.send(flag);})});//main.jsconst{fork}=require('child_process');app.use(async(ctx,next)=>{if(ctx.url==='/fetch'){constdata=ctx.request.body;//通知子进程开始执行任务,并传入数据constres=awaitcreatePromisefork('./child.js',data)}//创建异步线程函数creatPromisefork(childUrl,data){//加载子进程constres=fork(childUrl)//通知子进程开始工作data&&res.send(data)returnnewPromise(reslove=>{res.on('message',f=>{reslove(f)})})}awaitnext()})以上是实现父子进程通信的简单案例,我们的爬虫服务也会采用这种模式来实现。使用javascript手动控制爬虫的最大并发以上就是实现我们的爬虫应用需要考虑的技术问题。接下来正式实现业务功能。因为爬虫任务是在子流程中进行的,所以我们会在子流程代码中实现我们的爬虫功能。我们先梳理一下具体的业务需求,如下图:j'接下来我先解决控制爬虫最大并发数的问题。之所以解决这个问题,是为了考虑爬虫的性能。我们不能让爬虫抓取所有的网页,会启动很多并行进程进行处理,所以我们需要设计一个节流装置来控制每次的并发数。当前一个完成后,将获取下一批页面。具体代码实现如下://异步队列constqueue=[]//最大并发数constmax_parallel=6//起始指针letstart=0for(leti=0;i
