今天應該是爬蟲的最後一篇了。我們要把爬下來的資料做成「每日鐵人賽熱門 Top 10」。
// 2021-09-26.json
{
"12": [
{
"type": "Modern Web",
"series": "JavaScript Easy Go!",
"title": "#1 JavaScript Easy Go!",
"link": "https://ithelp.ithome.com.tw/articles/10264088",
"author":"JacobLinCool",
"date": [2021, 9, 15],
"view": 1286,
"team": "NTNU-Unic0rn"
},
...
],
"16": [
...
],
...
}
資料的格式:
type
, series
, title
, link
, author
, date
, view
, team
值team
會是 null
其實我們會發現這樣ˋ抓下來很多資料會重複,如果把文章資料分開存可以減少儲存空間消耗,但在這裡我們不再做資料格式的優化了,直接來處理資料。
我們想要做出來的東西是像這樣的:
需要計算的東西其實就只有一項:觀看增加值。
其他東西都只要從資料中抓出來就可以了!
先讓我們建立一個新檔案 report.js。
const fs = require("fs"); // 我們會需要 fs 來操作爬蟲爬到的檔案
const firstFilename = process.argv[2]; // 用 process.argv 來拿到要比較檔案的位置
const secondFilename = process.argv[3];
if (firstFilename && secondFilename) {
const [date1, date2] = [getDate(firstFilename), getDate(secondFilename)]; // 從檔名推論日期
const [d1, d2] = [getLatest(firstFilename), getLatest(secondFilename)]; // 拿到檔案中最新的資料
const result = calcDiff(d1, d2); // 計算差值
const report = genReport(result, date1, date2); // 根據結果產生報告
console.log(report); // 印出報告
} else { // 如果執行時沒有給兩個要比較的檔案位置,則提示使用方法
console.log("Usage: node report.js [day1 file path] [day2 file path]");
}
跟爬蟲一樣,我們需要先引入 fs 來做檔案操作。
接著從 process.agrv 中拿取要比較的兩個檔案位置, process.argv 會儲存從 Terminal 執行時的參數。
如果我們在 Terminal 這樣呼叫:
node report.js file1.json file2.json
則 process.agrv 就會是這樣:
["Node.js 的位置", "report.js 的位置", "file1.json", "file2.json"]
如果使用者有正確操作程式的話,程式就可以正常的產出報告;反之,則提醒使用者程式的正確使用方法。
接下來將分別說明各用到的函式
getDate
函式的任務是將檔名轉成日期。
function getDate(filename) {
const name = filename.split(/[\\/]/).pop().split(".")[0];
const [year, month, day] = name.split("-");
return [year, month, day];
}
filename.split(/[\\/]/).pop()
會得到不含路徑的單純檔案名稱,接著再用 .split(".")[0]
來拿到附檔名以外的 YYYY-MM-DD
格式,最後用 .split("-")
分割年月日。
getLatest
函式的任務則是從檔案中找到擁有最多文章的那個時間及資料,應該也就是「最新」的資料。
function getLatest(filename) {
const file = fs.readFileSync(filename, "utf8");
const json = JSON.parse(file);
const [time, data] = Object.entries(json).sort(([k1, v1], [k2, v2]) => v2.length - v1.length)[0];
return { time, data };
}
我們先讀取檔案並將其從文字轉成 Object。
接著用 Object.entries
轉成陣列再依文章數做降冪排序並使用第一個時間的資料。
calcDiff
負責計算兩筆數據之間的差值,並依差值大小排序。
function calcDiff(d1, d2) {
const { data: data1, time: time1 } = d1;
const { data: data2, time: time2 } = d2;
const _index = data1.reduce((acc, curr, idx) => {
acc[curr.link] = idx;
return acc;
}, {});
const diff = data2.map((item) => {
const d = _index[item.link] !== undefined ? item.view - data1[_index[item.link]].view : item.view;
return { ...item, diff: d };
});
return { data: diff.sort((a, b) => b.diff - a.diff), time: [time1, time2] };
}
注意:在這裡的 d1
d2
的 d 表示傾向於 data 而非 day 或 date。
我們先解構兩筆數據的時間及資料拉出 data1
data2
time1
time2
。
接著用 reduce
製作一個 data1
的臨時字典,讓我們之後在計算時能依照 data2
資料中的文章網址快速取得同文章於 data1
之數據。
接著遍歷 data2
來計算差值。
最後回傳時順便執行對差值的降冪排序。
genReport
顧名思義就是產生報告用的,因為 iThelp 文章是用 Markdown,所以報告也是產生 MD 語法。
function genReport(result, date1, date2) {
const { data, time } = result;
const articles = data.splice(0, 10);
let report = `-----\n## 每日鐵人賽熱門 Top 10 (${date1[1]}${date1[2]})\n以 ${+date1[1]}/${date1[2]} ${time[0]}:00 ~ ${+date2[1]}/${date2[2]} ${time[1]}:00 文章觀看數增加值排名\n\n`;
articles.forEach((item, idx) => {
report += `${idx + 1}. \`+${item.diff}\` [${item.title}](${item.link})\n * 作者: ${item.author}\n * 系列:${item.series}\n`;
});
return report;
}
我們用 splice(0, 10)
來取得已排序資料的前 10 名。report
則先用 Template 產生一些沒什麼用的資訊。
然後遍歷前 10 文章並加至 report
。
接著執行:
node report.js ./2021-09-25.json ./2021-09-26.json
成果就是你今天看到的「每日鐵人賽熱門 Top 10」啦!(但當然不包括我的評語)
以 9/25 20:00 ~ 9/26 20:00 文章觀看數增加值排名
+468
Day 1 無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
+394
Day 3 雲端四大平台比較:AWS . GCP . Azure . Alibaba
+387
Day 2 AWS 是什麼?又為何企業這麼需要 AWS 人才?
+365
Day 4 網路寶石:AWS VPC Region/AZ vs VPC/Subnet 關係介紹
+348
Day 5 網路寶石:AWS VPC 架構 Routes & Security (上)
+338
Day 15 儲存寶石:S3 架構 & 版本控管 (Versioning)
+335
Day 9 運算寶石:EC2 重點架構
+334
Day 6 網路寶石:AWS VPC 架構 Routes & Security (下)
+330
Day 8 網路寶石:【Lab】VPC外網 Public Subnet to the Internet (IGW) (下)
+330
Day 16 儲存寶石:S3 儲存類別 & 生命週期管理
我開始覺得 AWS 系列已經逐漸讓這個排行榜失去意義了...