Day19 - Crontab + Node.js

Crontab + Node.js

前言

前一篇我們對於 Crontab 已經有一個基本的概念,接下來當然要套用到實際案例上,不然認識了也沒用,所以這一篇我們就來簡單實作一個 Crontab + Node.js 的範例。

專案環境

首先我們先來建立一個專案,前面這個指令應該是苦瓜爛熟了,所以我就不多說了:

1
2
3
4
mkdir crontab-example-nodejs
cd crontab-example-nodejs
npm init -y
touch index.js

接下來我們還要額外安裝一個套件,也就是 cron

1
npm i cron

CronJob

Cron 的使用方式非常的簡單,依照官方範例文件所提供的程式碼來看,只要先引入 cron 套件,接著就可以使用 CronJob 這個類別來建立一個 CronJob。

1
2
3
4
5
6
7
8
9
10
11
var CronJob = require('cron').CronJob;

var job = new CronJob(
'* * * * * *',
function() {
console.log('You will see this message every second');
},
null,
true,
'America/Los_Angeles'
);

稍微有一點複雜吧?那我們就來一個一個解釋:

  • 第一個參數:這個參數就是剛剛我們所提到的 Crontab 格式,也就是說,這個參數就是用來設定你要執行的時間。
  • 第二個參數:這個參數就是你要執行的程式碼。
  • 第三個參數:完成排程後要執行的程式碼。
  • 第四個參數:是否立即執行。
  • 第五個參數:時區。

但實際上來講,我們比較常這樣寫

1
2
3
4
5
6
7
8
const CronJob = require('cron').CronJob;

const job = new CronJob(
'* * * * * *',
function() {
console.log('You will see this message every second');
}
);

因為三~五個參數都是選填的,所以我們可以不用寫,而第一個參數就是我們要設定的時間,第二個參數就是我們要執行的程式碼,接著最後再補上一個 job.start() 就可以了。

1
2
3
4
5
6
7
8
9
10
const CronJob = require('cron').CronJob;

const job = new CronJob(
'* * * * * *',
function() {
console.log('You will see this message every second');
}
);

job.start();

Note
由於前一篇已經有提到 Crontab 的格式,所以這邊就不再贅述,如果你還不清楚的話,可以回去看一下前一篇。

反之,如果你期望停止這個排程的話,那就可以使用 job.stop() 來停止。

搭配爬蟲

那麼你還記得我們在前面寫了兩隻爬蟲嗎?分別是:

  • 爬取鐵人賽參賽者列表
  • 爬取鐵人賽文章詳細資訊

透過 cron 的排程,我們就可以定時的去爬取資料,這樣就不用每次都要手動去執行了,而且也不用擔心忘記執行,所以我們就來實作一下吧!

首先我們先回顧一下前面寫的程式碼也就是 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// index.js
const fs = require('fs');

const cheerio = require('cheerio');

const getData = async (url) => {
try {
const response = await fetch(url);
const data = await response.text();
return data;
} catch (error) {
console.log(error);
}
}

const crawler = async () => {
const html = await getData('https://ithelp.ithome.com.tw/2022ironman/signup/list');
const $ = cheerio.load(html); // 載入 html
const paginationInner = $('span.pagination-inner > a').last(); // 取得最後一頁的頁碼
const lastPage = Number(paginationInner.text()); // 取得最後一頁的頁碼文字並轉成數字
const data = [];

// 用 for 迴圈來爬取每一頁的資料
for(let i = 1; i <= lastPage; i++) {
console.log(`正在爬取第 ${i} 頁`);
const html = await getData(`https://ithelp.ithome.com.tw/2022ironman/signup/list?page=${i}`);

const $ = cheerio.load(html); // 載入 html

const listCard = $('.list-card');

listCard.each((index, element) => {
const name = $(element).find('.contestants-list__name').text();
const category = $(element).find('.tag span').text();
const title = $(element).find('.contestants-list__title').text();
const url = $(element).find('.contestants-list__title').attr('href');
data.push({
name,
category,
title,
url,
});
});

// 避免過度請求增加伺服器負擔
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 5000); // 5 秒跑一次
})
}

fs.writeFileSync('./data.json', JSON.stringify(data));
}

crawler();

前面這一個程式碼就是用來爬取鐵人賽參賽者列表的,而我們要做的就是把這個程式碼改成可以定時執行的,但其實用法很簡單,只要把程式碼包在 CronJob 裡面就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// index.js
const fs = require('fs');
const CronJob = require('cron').CronJob;
const cheerio = require('cheerio');

const getData = async (url) => {
// ... 略過程式碼
}

const crawler = async () => {
// ... 略過程式碼
}

const job = new CronJob(
'* * * * * *',
function() {
crawler();
}
);

這樣子我們就可以達到每分鐘爬取一次的效果,但實際上來講,我們並不需要那麼頻繁的撈取資料,所以我們可以改成每個禮拜一早上九點爬取一次

1
2
3
4
5
6
7
8
9
10
11
12
// index.js

// ... 略過程式碼

const job = new CronJob(
'0 9 * * 1',
function() {
crawler();
}
);

job.start();

那麼另一個爬取鐵人賽文章詳細資訊的程式碼也是一樣的,只是這邊就留給你自己試試看了,我就不多做說明與解釋囉~

混亂知識點

現在應該會發生一點混亂點,也就是 Node.js 的 Cron 跟 Linux 的 Cron 有什麼差別呢?

混亂知識點

首先我們在 npm 所下載安裝的 cron 套件,主要是運行於 Node.js 環境下的,而 Linux 則是一個作業系統,所以這兩個東西是不太一樣的東西,只是概念以及時間格式是類似相同的,所以有時候會讓人有點混淆。

因此通常你會比較常看到人家講 node-cron,但只是因為你在安裝 npm 套件時,指令是這樣…

1
npm i cron

所以才會有一點混亂,但其實這兩個東西是不一樣的,只是概念上有點類似而已。

那這兩者有什麼明顯差異嗎?剛才有提到概念以及時間的格式非常雷同,但是大多在 Linux 上的 Cron 比較常用來執行 Shell Script,所以與 Node.js 的 Cron 相比會比較難以擴充成更複雜的邏輯。

Note
Shell Script 是一個純文字檔案,裡面可以寫一些指令,例如 lscdmkdir 等等,而這些指令都是可以在終端機上執行的,所以你可以把 Shell Script 想像成一個可以在終端機上執行的檔案。

簡而總之,如果你想要在 Node.js 上執行自己所撰寫的 JavaScript 程式碼,那就使用 node-cron,如果你想要在 Linux 上執行 Shell Script,那就使用 Linux 的 Cron。

那麼這一篇也差不多了,我們就先到這邊結束,我們下一篇見囉~