前言:根据MOOCKoa2,实现电影微信公众号前端和前端开发的改造。因为上下班的时候可以看小说,但是无奈广告太多了,而且要收费,所以结合课程,开发了,上传到我的微信小程序。github的总体思路:1.连接数据库2.运行定时任务更新数据库3.开启接口服务4.微信小程序接口调用1.连接数据库,连接本地mongodb数据库constmongoose=require('mongoose')vardb='mongodb://localhost/story-bookShelf'exports.connect=()=>{让maxConnectTimes=0returnnewPromise((resolve,reject)=>{if(process.env.NODE_ENV!=='production'){mongoose.set('debug',false)}mongoose.connect(db)mongoose.connection.on('disconnected',()=>{maxConnectTimes++if(maxConnectTimes<5){mongoose.connect(db)}else{thrownewError('Thedatabaseisdown,let'sfixit')}})mongoose.connection.on('error',err=>{console.log(err)maxConnectTimes++if(maxConnectTimes<5){mongoose.connect(db)}else{thrownewError('数据库已关闭,让我们修复它')}})mongoose.connection.once('open',()=>{resolve()console.log('MongoDBConnectedsuccessfully!')})})}然后初始化定义好的Schemaconstmongoose=require('mongoose')constSchema=mongoose.SchemaconstbookSchema=newSchema({name:{type:String},bookId:{unique:true,type:Number}})......mongoose.model('Book',bookSchema)2.运行计划任务并更新数据库。这一步主要是定时更新数据库中的小说章节。Node-schedule用于执行计划任务。小说章节数是否增加不需要爬取。同时,爬取的时候需要提前爬取前5章,避免一些作者为了占坑而提前写的预告。每部小说都会运行一个子进程child_process,将数据存储到mongo中,存储子进程对后续有帮助。定时运行任务时,之前的任务还在运行,所以每次运行前,清除存储的子进程,kill掉子进程。章节任务//chapter.jsconstcp=require('child_process')const{resolve}=require('path')constmongoose=require('mongoose')const{childProcessStore}=require('../lib/child_process_store')//全局存储子进程/****@param{bookID}bookId*@param{从哪里开始看}startNum*/exports.taskChapter=async(bookId,startNum=0)=>{constChapter=mongoose.model('Chapter')constscript=resolve(__dirname,'../crawler/chapter.js')//实际执行爬虫任务模块constchild=cp.fork(script,[])//打开IPCchannel,transferdataletinvoked=false//等待子进程传回数据,然后存入mongo(具体爬取看下一段代码)child.on('message',asyncdata=>{//先找有没有数据letchapterData=awaitChapter.findOne({chapterId:data.chapterId})//需要将获取的章节和存储的章节进行比较,防止作者占坑if(!chapterData){chapterData=newChapter(data)awaitchapterData.save()return}//比较字数,相差50个字符if((data.content.length-50>=0)&&(data.content.length-50>章节数据.content.length)){Chapter.updateOne({chapterId:+data.chapterId},{content:data.content});}})child.send({//发送给子进程抓取bookId,//whereThisnovelstartNum//从哪一章开始抓取})//存储运行进程抓取的所有章节并删除子进程childProcessStore.set('chapter',child)}真正开始爬取,使用puppeteer,谷歌内核爬取,很强大分两步:1.爬取小说对应的章节目录,获取章节数组2.根据传入的startNum爬取章节startNum//crawler/chapter.jsconstpuppeteer=require('puppeteer')leturl=`http://www.mytxt.cc/read/`//目标URLconstsleep=time=>newPromise(resolve=>{setTimeout(resolve,time)})process.on('message',asyncbook=>{url=`${url}${book.bookId}/`console.log('开始访问目标页面---章节',url)//查找对应的小说并获取具体章节数组constbrowser=awaitpuppeteer.launch({args:['--no-sandbox'],dumpio:false}).catch(err=>{console.log('browser--error:',err)browser.close})constpage=awaitbrowser.newPage()awaitpage.goto(url,{waitUntil:'networkidle2'})awaitsleep(3000)awaitpage.waitForSelector('.story_list_m62topxs')//找到特定字段的类letresult=awaitpage.evaluate((book)=>{letlist=document.querySelectorAll('.cp_dd_m62topxsli')letreg=newRegExp(`${book.bookId}\/(\\S*).html`)letchapter=Array.from(list).map((item,iindex)=>{return{title:item.innerText,chapterId:item.innerHTML.match(reg)[1]}})returnchapter},book)//拦截从哪里开始爬取章节lettempResult=result.slice(book.startNum,result.length)for(leti=0;i
