最近项目需要一些资讯,因为 项目 是用 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 内容,我们可以使用 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 解析都不需要了,进行一下简单的封装,然后就可以像这样使用了: #滑稽 :)
- await new Downloader('http://36kr.com/newsflashes', DOWNLOADER.puppeteer).downloadHTML()
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 下来看就好了
来源: https://juejin.im/post/5a506e6d51882573450156e3