iT邦幫忙

2021 iThome 鐵人賽

DAY 12
2
Modern Web

JavaScript Easy Go!系列 第 12

#12 Web Crawler 5

今天應該是爬蟲的最後一篇了。我們要把爬下來的資料做成「每日鐵人賽熱門 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

其實我們會發現這樣ˋ抓下來很多資料會重複,如果把文章資料分開存可以減少儲存空間消耗,但在這裡我們不再做資料格式的優化了,直接來處理資料。

我們想要的東西

我們想要做出來的東西是像這樣的:

  1. +300 Article 1
    • 作者: Author 1
    • 系列:Series 1
  2. +200 Article 2
    • 作者: Author 2
    • 系列:Series 2
  3. +100 Article 3
    • 作者: Author 3
    • 系列:Series 3

需要計算的東西其實就只有一項:觀看增加值。
其他東西都只要從資料中抓出來就可以了!

寫程式

先讓我們建立一個新檔案 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

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

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

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

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」啦!(但當然不包括我的評語)


每日鐵人賽熱門 Top 10 (0925)

以 9/25 20:00 ~ 9/26 20:00 文章觀看數增加值排名

  1. +468 Day 1 無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  2. +394 Day 3 雲端四大平台比較:AWS . GCP . Azure . Alibaba
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  3. +387 Day 2 AWS 是什麼?又為何企業這麼需要 AWS 人才?
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  4. +365 Day 4 網路寶石:AWS VPC Region/AZ vs VPC/Subnet 關係介紹
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  5. +348 Day 5 網路寶石:AWS VPC 架構 Routes & Security (上)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  6. +338 Day 15 儲存寶石:S3 架構 & 版本控管 (Versioning)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  7. +335 Day 9 運算寶石:EC2 重點架構
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  8. +334 Day 6 網路寶石:AWS VPC 架構 Routes & Security (下)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  9. +330 Day 8 網路寶石:【Lab】VPC外網 Public Subnet to the Internet (IGW) (下)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  10. +330 Day 16 儲存寶石:S3 儲存類別 & 生命週期管理
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題

我開始覺得 AWS 系列已經逐漸讓這個排行榜失去意義了...


上一篇
#11 Web Crawler 4
下一篇
#13 Automation
系列文
JavaScript Easy Go!31

尚未有邦友留言

立即登入留言