一般我们说写爬虫,第一个想到的编程语言毫无疑问是python。而作为一个熟悉js语言的开发者,在面对一些简单的爬虫需求时,nodejs也不失为一个不错的替代方案,因为减少了语法切换带来的精力负担。好,让我们开始吧。
说明:仓库代码在https://gitee.com/bulls-cows/node-crawler。
目录
一、明确需求
作为一名程序员,你肯定听过Github,国内有个类似的网站叫码云(gitee),相信不少朋友也都是听过的,在这个网址下https://gitee.com/explore,我们可以看到一些gitee的推荐项目,截图如下:
我们这次的目标任务就是定时抓取这些推荐项目。
二、爬取数据
爬虫爬虫,顾名思义得爬数据。这里我们需要用到2个npm包:superagent和cheerio。superagent是一个用来发请求的包。cheerio是一个可以用来从html代码片段里选择需要的元素的包,其api类似浏览器端的jQuery。
const cheerio = require("cheerio"); const superagent = require("superagent"); const targetDomain = 'https://gitee.com' const targetUrl = `${targetDomain}/explore` function getRecommendedProjectsInGiteeExplore () { return new Promise((resolve, reject) => { superagent .get(targetUrl) .set('Host', 'gitee.com') .set('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9') .set('Referer', 'https://gitee.com/organizations/bulls-cows/projects') .set('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36') .end((err, res) => { if (err) { reject(err) return } const $ = cheerio.load(res.text); const list = [] $(".explore-repo__list > .item").each((index, element) => { const $this = $(element) const title = $this.find('.project-title .title').text().trim() const href = `${targetDomain}${$this.find('.project-title .title').prop('href').trim()}` const metas = (() => { const labels = [] $this.find('.project-title .project-meta .label').each((idx, elem) => { labels.push($(elem).text().trim()) }) return labels })() const desc = $this.find('.project-desc').text().trim() const updated = $this.find('.project-latest .text-muted:nth-of-type(1)').text().trim() const returnObj = { title, href, metas, desc, updated } list.push(returnObj) }) const time = new Date().toLocaleString() resolve({ time, list }) }); }); } module.exports = { getRecommendedProjectsInGiteeExplore, }
三、数据存储
我们以mysql为例,先封装一个sql方法用于执行sql语句:
const mysql = require('mysql'); const mysqlConfig = require('../config/database.config') const connection = mysql.createConnection(mysqlConfig); sql = ({query = '', params = []}) => new Promise((resolve, reject) => { connection.query(query, params, function (error, results, fields) { if (error) { reject(error) return } resolve(results) }); }) module.exports = {sql}
然后用类似下面的方式来执行sql(具体的sql语句需要根据你的需要做调整):
const { sql } = require('./services/sql') sql({ query: 'UPDATE example_table_name SET name = ?, time = ? WHERE id = ?', params: [name, Math.round(time / 1000), 1], }) .then((result) => { logs.push(`sql结果:${result.message.replace(/^\(/, '')}`) }) .catch((error) => { throw error }) .then(() => { // })
四、定时执行
一般我们写出来的爬虫很少是只需要爬一次的,经常会需要定时进行爬取,如果每次都手动去执行脚本未免显得有点傻了吧唧的。我们可以使用node-schedule这个npm包。代码示例如下:
const nodeSchedule = require("node-schedule"); const { getRecommendedProjectsInGiteeExplore } = require('./jobs/gitexplore') // 每10秒执行一次任务 nodeSchedule.scheduleJob('*/10 * * * * *', async function () { try { const { time, list } = await getRecommendedProjectsInGiteeExplore(); console.log(`爬取时间:${time}`) console.log(JSON.stringify(list, null, 2)) } catch (error) { console.error(error); } });
五、正式部署
在正式服务器上我们直接把上面的代码丢到服务器上运行多少有些不妥,比如服务挂了不会自动重启,比如服务器重启后我们的程序就停止运行了,这些都是部署正式项目需要注意的点。这里我们用pm2部署项目。由于pm2不是本文的重点,所以下面只是简单给出部署的代码和操作说明。
先在项目根目录下添加ecosystem.config.js文件,内容如下:
module.exports = { apps: [ { name: `node-crawler`, script: './src/index.js', // Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/ args: '', instances: 1, autorestart: true, watch: [ 'src', ], watch_delay: 1000, ignore_watch: [ 'node_modules', 'logs', ], max_memory_restart: '200M', env: { // pm2 start ecosystem.config.js NODE_ENV: 'production', }, error_file: 'logs/err.log', out_file: 'logs/out.log', log_file: 'logs/combined.log', log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS', }, ], }
然后在package.json的scripts字段下添加如下内容:
"scripts": { "installDependencies": "npm i -g pm2 && npm i", "debug": "node src/index.js", "start": "pm2 start ecosystem.config.js", "restart": "pm2 restart ecosystem.config.js", "delete": "pm2 delete ecosystem.config.js", "stop": "pm2 stop ecosystem.config.js", "getShellUsedToStartProjectAfterReboot": "pm2 startup", "log": "pm2 log", "status": "pm2 status" },
在部署前,需要先安装依赖包,如果服务器上之前装过pm2可以执行npm install来安装,否则可以执行npm run installDependencies来进行安装,后者会在npm install之外另外全局安装pm2命令行工具。
安装完依赖包后,可以执行npm run start来通过pm2启动我们的项目,这样项目如果挂了pm2会自动重启项目。
最后,我们再执行npm run getShellUsedToStartProjectAfterReboot,这样服务器重启后我们的项目也会自动重启。
六、说明
*声明下,nodejs是js的一个运行时,本文里的nodejs只是一种习惯用法,并不表示nodejs是一门语言,本文中的nodejs可以理解为在nodejs运行时下执行的js。