Koa2 + Mongo + 爬虫 搭建 小说微信小程序(本地开发篇)
前言:根据慕课网 Koa2 实现电影微信公众号前后端开发 学习后的改造
由于上下班期间会看会小说,但是无奈广告太多,还要收费,于是结合课程,进行开发,并上传到自己的微信小程序。
大致的思路:
1.连接数据库
2.跑定时任务,进行数据库的更新
3.开启接口服务
4.微信小程序接口调用
1.连接数据库
连接本地的mongodb数据库
const mongoose = require('mongoose') var db = 'mongodb://localhost/story-bookShelf' exports.connect = () => { let maxConnectTimes = 0 return new Promise((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 { throw new Error('数据库挂了吧,快去修吧') } }) mongoose.connection.on('error', err => { console.log(err) maxConnectTimes++ if (maxConnectTimes < 5) { mongoose.connect(db) } else { throw new Error('数据库挂了吧,快去修吧') } }) mongoose.connection.once('open', () => { resolve() console.log('MongoDB Connected successfully!') }) }) }
然后初始化定义好的Schema
const mongoose = require('mongoose') const Schema = mongoose.Schema const bookSchema = new Schema({ name: { type: String }, bookId: { unique: true, type: Number } }) ...... mongoose.model('Book', bookSchema)
2.跑定时任务,进行数据库的更新
这一步骤主要是在定时进行数据库小说章节的更新,用的是 node-schedule进行定时跑任务。
- 小说章节数是否增加,没增加不用进行爬取。同时在爬取的时候需要提前前5章爬取,避免一些作者为了占坑,提前写的预告。
- 每一本小说就开一个子进程child_process去跑,将数据存储到mongo, 同时存储子进程对后续有用。
- 定时跑任务时候会遇到上一条任务还在跑,所以在每一次跑之前都清空一遍储存的子进程,将子进程杀掉。
章节任务
// chapter.js const cp = require('child_process') const { resolve } = require('path') const mongoose = require('mongoose') const { childProcessStore } = require('../lib/child_process_store') // 全局存储子进程 /** * * @param {书本ID} bookId * @param {从哪里开始查找} startNum */ exports.taskChapter = async(bookId, startNum = 0) => { const Chapter = mongoose.model('Chapter') const script = resolve(__dirname, '../crawler/chapter.js') // 真正执行爬虫任务模块 const child = cp.fork(script, []) // 开启IPC通道,传递数据 let invoked = false // 这里等子进程将数据传回来,然后存储到mongo中(具体爬取看下一段代码) child.on('message', async data => { // 先找一下是否有数据了 let chapterData = await Chapter.findOne({ chapterId: data.chapterId }) // 需要将拿到的章节与存储的章节做对比 防止作者占坑 if (!chapterData) { chapterData = new Chapter(data) await chapterData.save() return } // 进行字数对比 相差50字符 if ((data.content.length - 50 >= 0) && (data.content.length - 50 > chapterData.content.length)) { Chapter.updateOne ( { chapterId: +data.chapterId }, { content : data.content } ); } }) child.send({ // 发送给子进程进行爬取 bookId, // 哪本小说 startNum // 从哪个章节开始爬 }) // 存储所有章节的爬取 用于跑进程删除子进程 childProcessStore.set('chapter', child) }
真正开启爬虫,用的是 puppeteer,谷歌内核的爬取,功能很强大。
分两步:
1.爬对应小说的章节目录,拿到章节数组
2.根据传进来的startNum 进行章节startNum 的往后爬取
// crawler/chapter.js const puppeteer = require('puppeteer') let url = `http://www.mytxt.cc/read/` // 目标网址 const sleep = time => new Promise(resolve => { setTimeout(resolve, time) }) process.on('message', async book => { url = `${url}${book.bookId}/` console.log('Start visit the target page --- chapter', url) // 找到对应的小说,拿到具体的章节数组 const browser = await puppeteer.launch({ args: ['--no-sandbox'], dumpio: false }).catch(err => { console.log('browser--error:', err) browser.close }) const page = await browser.newPage() await page.goto(url, { waitUntil: 'networkidle2' }) await sleep(3000) await page.waitForSelector('.story_list_m62topxs') // 找到具体字段的class let result = await page.evaluate((book) => { let list = document.querySelectorAll('.cp_dd_m62topxs li') let reg = new RegExp(`${book.bookId}\/(\\S*).html`) let chapter = Array.from(list).map((item, index) => { return { title: item.innerText, chapterId: item.innerHTML.match(reg)[1] } }) return chapter }, book) // 截取从哪里开始爬章节 let tempResult = result.slice(book.startNum, result.length) for (let i = 0; i < tempResult.length; i++) { let chapterId = tempResult[i].chapterId console.log('开始爬url:', `${url}${chapterId}.html`) await page.goto(`${url}${chapterId}.html`, { waitUntil: 'networkidle2' }) await sleep(2000) const content = await page.evaluate(() => { return document.querySelectorAll('.detail_con_m62topxs p')[0].innerText }) tempResult[i].content = content tempResult[i].bookId = book.bookId process.send(tempResult[i]) // 通过IPC将数据传回去,触发child.on('message') } browser.close() process.exit(0) })
3.开启接口
做的任务主要是,拿mongodb的数据,同时通过koa-router发布路由
先定义好路由装饰器,方便后续使用 具体看 decorator.js
底层拿到数据库的数据
service/book.js // 拿到数据库存储的值 const Chapter = mongoose.model('Chapter') // 获取具体的章节内容 export const getDetailChapter = async (data) => { const chapter = await Chapter.findOne({ chapterId: data.chapterId, bookId: data.bookId }, { content: 1, title: 1, chapterId: 1 }) // console.log('getDetailChapter::', chapter) return chapter } ...
路由定义 后续的接口就是 ‘/api/book/chapter’
@controller('/api/book') export class bookController { @post('/chapter') async getDetailChapter (ctx, next) { const { chapterId, bookId } = ctx.request.body.data const list = await getDetailChapter({ chapterId, bookId }) ctx.body = { success: true, data: list } } }
4.微信小程序
使用wepy进行开发,功能也是很简单,具体开发可以参见小程序代码,这里不做详细讲述。
支持记录每一章的进度,与全局设置。后续可以自己发挥。
在目标网站找到小说的Id之后就能进行查找了。
接下来讲解部署到服务器细节。
最后,在这里特别感谢@汪江 江哥的帮助,我前后琢磨了两个月,而他就用了三天,谢谢你不厌其烦的帮助,与你共事很开心。
以上只是我的不成熟的技术,欢迎各位留言指教。
相关推荐
浪味仙 2020-06-03
cdkey 2020-05-04
jieq 2020-11-09
ZZZhangbingyi 2020-08-26
haixianTV 2020-08-15
Develop 2020-06-25
郴州小程序 2020-06-13
huningjun 2020-06-12
zuoliangzhu 2020-06-11
hgzhang 2020-06-04
powderhose 2020-06-02
cdkey 2020-05-29
戴翔的技术 2020-05-27
郴州小程序 2020-05-26
cdkey 2020-05-26
sucheng 2020-05-25
newhappy 2020-05-16
cbao 2020-05-12
cbao 2020-04-26