前言:根據慕課網 Koa2 實現電影微信公衆號先後端開發 學習後的改造html
因爲上下班期間會看會小說,可是無奈廣告太多,還要收費,因而結合課程,進行開發,並上傳到本身的微信小程序。node
githubgit
大體的思路:
1.鏈接數據庫
2.跑定時任務,進行數據庫的更新
3.開啓接口服務
4.微信小程序接口調用github
鏈接本地的mongodb數據庫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)
這一步驟主要是在定時進行數據庫小說章節的更新,用的是 node-schedule進行定時跑任務。npm
章節任務小程序
// 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) })
作的任務主要是,拿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 } } }
使用wepy進行開發,功能也是很簡單,具體開發能夠參見小程序代碼,這裏不作詳細講述。
支持記錄每一章的進度,與全局設置。後續能夠本身發揮。
在目標網站找到小說的Id以後就能進行查找了。
接下來說解部署到服務器細節。
最後,在這裏特別感謝@汪江 江哥的幫助,我先後琢磨了兩個月,而他就用了三天,謝謝你不厭其煩的幫助,與你共事很開心。以上只是個人不成熟的技術,歡迎各位留言指教。