最近项目须要一些资讯,由于项目是用
Node.js
来写的,因此就天然地用Node.js
来写爬虫了node
项目地址:github.com/mrtanweijie… ,项目里面爬取了Readhub、开源中国、开发者头条、36Kr这几个网站的资讯内容,暂时没有对多页面进行处理,由于天天爬虫都会跑一次,如今每次获取到最新的就能够知足需求了,后期再进行完善git
爬虫流程归纳下来就是把目标网站的HTML下载到本地再进行数据提取。github
Node.js
有不少http请求库,这里使用request
,主要代码以下:npm
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内容,咱们可使用Google的puppeteer
来下载客户端渲染的网站页面。众所周知的缘由,在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解析都不须要了,进行一下简单的封装,而后就能够像这样使用了: #滑稽 :)bash
await new Downloader('http://36kr.com/newsflashes', DOWNLOADER.puppeteer).downloadHTML()
复制代码
HTML内容提取固然是使用神器cheerio
了,cheerio
暴露了和jQuery
同样的接口,用起来很是简单。浏览器打开页面F12查看提取的页面元素节点,而后根据需求来提取内容便可less
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
天天跑一跑dom
function job () {
let cronJob = new cron.CronJob({
cronTime: cronConfig.cronTime,
onTick: () => {
spider()
},
start: false
})
cronJob.start()
}
复制代码
数据持久化理论上应该不属于爬虫关心的范围,用mongoose
,建立Modelasync
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)
复制代码
基本操做mongoose
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下来看就行了