最近項(xiàng)目需要一些資訊,因?yàn)轫?xiàng)目是用 Node.js 來(lái)寫(xiě)的,所以就自然地用 Node.js 來(lái)寫(xiě)爬蟲(chóng)了
創(chuàng)新互聯(lián)主要從事成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)泰寧,10余年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專(zhuān)業(yè),歡迎來(lái)電咨詢(xún)建站服務(wù):13518219792
項(xiàng)目地址:github.com/mrtanweijie… ,項(xiàng)目里面爬取了 Readhub 、 開(kāi)源中國(guó) 、 開(kāi)發(fā)者頭條 、 36Kr 這幾個(gè)網(wǎng)站的資訊內(nèi)容,暫時(shí)沒(méi)有對(duì)多頁(yè)面進(jìn)行處理,因?yàn)槊刻炫老x(chóng)都會(huì)跑一次,現(xiàn)在每次獲取到最新的就可以滿(mǎn)足需求了,后期再進(jìn)行完善
爬蟲(chóng)流程概括下來(lái)就是把目標(biāo)網(wǎng)站的HTML下載到本地再進(jìn)行數(shù)據(jù)提取。
一、下載頁(yè)面
Node.js 有很多http請(qǐng)求庫(kù),這里使用 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 來(lái)進(jìn)行包裝,便于后面使用的時(shí)候用上 async/await 。因?yàn)橛泻芏嗑W(wǎng)站是在客戶(hù)端渲染的,所以下載到的頁(yè)面不一定包含想要的HTML內(nèi)容,我們可以使用 Google 的 puppeteer 來(lái)下載客戶(hù)端渲染的網(wǎng)站頁(yè)面。眾所周知的原因,在 npm i 的時(shí)候 puppeteer 可能因?yàn)樾枰螺dChrome內(nèi)核導(dǎo)致安裝會(huì)失敗,多試幾次就好了:)
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) } }) }
當(dāng)然客戶(hù)端渲染的頁(yè)面最好是直接使用接口請(qǐng)求的方式,這樣后面的HTML解析都不需要了,進(jìn)行一下簡(jiǎn)單的封裝,然后就可以像這樣使用了: #滑稽 :)
await new Downloader('http://36kr.com/newsflashes', DOWNLOADER.puppeteer).downloadHTML()
二、HTML內(nèi)容提取
HTML內(nèi)容提取當(dāng)然是使用神器 cheerio 了, cheerio 暴露了和 jQuery 一樣的接口,用起來(lái)非常簡(jiǎn)單。瀏覽器打開(kāi)頁(yè)面 F12 查看提取的頁(yè)面元素節(jié)點(diǎn),然后根據(jù)需求來(lái)提取內(nèi)容即可
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 }
三、定時(shí)任務(wù)
cron 每天跑一跑 function job () { let cronJob = new cron.CronJob({ cronTime: cronConfig.cronTime, onTick: () => { spider() }, start: false }) cronJob.start() }
四、數(shù)據(jù)持久化
數(shù)據(jù)持久化理論上應(yīng)該不屬于爬蟲(chóng)關(guān)心的范圍,用 mongoose ,創(chuàng)建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)
愉快地保存數(shù)據(jù)
await newsService.batchSave(newsListTem)
更多內(nèi)容到Github把項(xiàng)目clone下來(lái)看就好了。
總結(jié)