使用 Node.js 来开发一个资讯爬虫

3,913

最近项目需要一些资讯,因为项目是用Node.js来写的,所以就自然地用Node.js来写爬虫了

项目地址:github.com/mrtanweijie… ,项目里面爬取了Readhub开源中国开发者头条36Kr这几个网站的资讯内容,暂时没有对多页面进行处理,因为每天爬虫都会跑一次,现在每次获取到最新的就可以满足需求了,后期再进行完善

爬虫流程概括下来就是把目标网站的HTML下载到本地再进行数据提取。

一、下载页面

Node.js有很多http请求库,这里使用request,主要代码如下:

 requestDownloadHTML () {
    const options = {
      url: this.url,
      headers: {
        'User-Agent': this.randomUserAgent()
      }
    }
    return new Promise((resolve, reject) => {
      request(options, (err, response, body) => {
        if (!err && response.statusCode === 200) {
          return resolve(body)
        } else {
          return reject(err)
        }
      })
    })
  }

使用Promise来进行包装,便于后面使用的时候用上async/await。因为有很多网站是在客户端渲染的,所以下载到的页面不一定包含想要的HTML内容,我们可以使用Googlepuppeteer来下载客户端渲染的网站页面。众所周知的原因,在npm i 的时候puppeteer可能因为需要下载Chrome内核导致安装会失败,多试几次就好了:)

  puppeteerDownloadHTML () {
    return new Promise(async (resolve, reject) => {
      try {
        const browser = await puppeteer.launch({ headless: true })
        const page = await browser.newPage()
        await page.goto(this.url)
        const bodyHandle = await page.$('body')
        const bodyHTML = await page.evaluate(body => body.innerHTML, bodyHandle)
        return resolve(bodyHTML)
      } catch (err) {
        console.log(err)
        return reject(err)
      }
    })
  }

当然客户端渲染的页面最好是直接使用接口请求的方式,这样后面的HTML解析都不需要了,进行一下简单的封装,然后就可以像这样使用了: #滑稽 :)

 await new Downloader('http://36kr.com/newsflashes', DOWNLOADER.puppeteer).downloadHTML()

二、HTML内容提取

HTML内容提取当然是使用神器cheerio了,cheerio暴露了和jQuery一样的接口,用起来非常简单。浏览器打开页面F12查看提取的页面元素节点,然后根据需求来提取内容即可

 readHubExtract () {
    let nodeList = this.$('#itemList').find('.enableVisited')
    nodeList.each((i, e) => {
      let a = this.$(e).find('a')
      this.extractData.push(
        this.extractDataFactory(
          a.attr('href'),
          a.text(),
          '',
          SOURCECODE.Readhub
        )
      )
    })
    return this.extractData
  }

三、定时任务

cron每天跑一跑

function job () {
  let cronJob = new cron.CronJob({
    cronTime: cronConfig.cronTime,
    onTick: () => {
      spider()
    },
    start: false
  })
  cronJob.start()
}

四、数据持久化

数据持久化理论上应该不属于爬虫关心的范围,用mongoose,创建Model

import mongoose from 'mongoose'
const Schema = mongoose.Schema
const NewsSchema = new Schema(
  {
    title: { type: 'String', required: true },
    url: { type: 'String', required: true },
    summary: String,
    recommend: { type: Boolean, default: false },
    source: { type: Number, required: true, default: 0 },
    status: { type: Number, required: true, default: 0 },
    createdTime: { type: Date, default: Date.now }
  },
  {
    collection: 'news'
  }
)

export default mongoose.model('news', NewsSchema)

基本操作

import { OBJ_STATUS } from '../../Constants'
class BaseService {
  constructor (ObjModel) {
    this.ObjModel = ObjModel
  }

  saveObject (objData) {
    return new Promise((resolve, reject) => {
      this.ObjModel(objData).save((err, result) => {
        if (err) {
          return reject(err)
        }
        return resolve(result)
      })
    })
  }
}
export default BaseService

资讯

import BaseService from './BaseService'
import News from '../models/News'
class NewsService extends BaseService {}
export default new NewsService(News)

愉快地保存数据

await newsService.batchSave(newsListTem)

更多内容到Github把项目clone下来看就好了