资讯专栏INFORMATION COLUMN

Koa2 + Mongo + 爬虫 搭建 小说微信小程序(本地开发篇)

Kross / 1071人阅读

摘要:前言根据慕课网实现电影微信公众号前后端开发学习后的改造由于上下班期间会看会小说,但是无奈广告太多,还要收费,于是结合课程,进行开发,并上传到自己的微信小程序。

前言:根据慕课网 Koa2 实现电影微信公众号前后端开发 学习后的改造

由于上下班期间会看会小说,但是无奈广告太多,还要收费,于是结合课程,进行开发,并上传到自己的微信小程序。

github

大致的思路:
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之后就能进行查找了。
接下来讲解部署到服务器细节。

最后,在这里特别感谢@汪江 江哥的帮助,我前后琢磨了两个月,而他就用了三天,谢谢你不厌其烦的帮助,与你共事很开心。
以上只是我的不成熟的技术,欢迎各位留言指教。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100334.html

相关文章

  • Koa2 + Mongo + 爬虫 搭建 小说信小程序本地开发)---编码时遇到的问题

    摘要:更新于利用重构了下此项目,有兴趣的可以参考看看,传送门首先感谢作者的分享,很赞,原文地址这里记录下编码遇到的问题,这里只针对进行了验证。 更新于 2019-01-02 利用 eggjs 重构了下此项目,有兴趣的可以参考看看,传送门 首先感谢作者的分享,很赞~,原文地址 这里记录下编码遇到的问题,这里只针对 sever 进行了验证。有同样遇到问题的童鞋,可以作为参照~ 本地环境: ...

    liangdas 评论0 收藏0
  • RN+dva+node+mongo+nginx+docker 从开发到部署,全栈入坑指引!

    摘要:基本功能提供小说操作相关的所有提供登录注册相关实现验证码定期自动更新小说爬虫部署运行即可实现一键部署。如果还想更近一步的实现自动部署的话,可以试试开源免费。 项目地址 前言 作为一个优秀前端er,除了要精通前端基础外,其他的如后台,运维,linux等都要有所了解。这样你才能对自己所负责的项目有一个整体的把握,不同端开发思维的碰撞,有助于你形成良好的代码习惯,写出高效优质的代码。话不多说...

    liaorio 评论0 收藏0
  • 2017年1月前端月报

    摘要:平日学习接触过的网站积累,以每月的形式发布。年以前看这个网址概况在线地址前端开发群月报提交原则技术文章新的为主。 平日学习接触过的网站积累,以每月的形式发布。2017年以前看这个网址:http://www.kancloud.cn/jsfron... 概况 在线地址:http://www.kancloud.cn/jsfront/month/82796 JS前端开发群月报 提交原则: 技...

    FuisonDesign 评论0 收藏0
  • 2017年1月前端月报

    摘要:平日学习接触过的网站积累,以每月的形式发布。年以前看这个网址概况在线地址前端开发群月报提交原则技术文章新的为主。 平日学习接触过的网站积累,以每月的形式发布。2017年以前看这个网址:http://www.kancloud.cn/jsfron... 概况 在线地址:http://www.kancloud.cn/jsfront/month/82796 JS前端开发群月报 提交原则: 技...

    Jochen 评论0 收藏0
  • 2017年1月前端月报

    摘要:平日学习接触过的网站积累,以每月的形式发布。年以前看这个网址概况在线地址前端开发群月报提交原则技术文章新的为主。 平日学习接触过的网站积累,以每月的形式发布。2017年以前看这个网址:http://www.kancloud.cn/jsfron... 概况 在线地址:http://www.kancloud.cn/jsfront/month/82796 JS前端开发群月报 提交原则: 技...

    ivyzhang 评论0 收藏0

发表评论

0条评论

Kross

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<