#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 系列已经逐渐让这个排行榜失去意义了...


<<:  Day 11: QwikLabs

>>:  【没钱买ps,PyQt自己写】Day 11 - 以 Qlabel 在 PyQt 中显示图片 (基於 QImage 使用 OpenCV)

JavaScript Day05 - 比较与逻辑判断运算子

比较运算子 >、<、>=、<=:分别为「大於」、「小於」、「大於等於」、「小...

[Day 12] 当冲实验结果概述

一、总结 总结来说,今天研究了一整天论文, 该篇论文对蒐集5分线数据,并以此预测之後的股价倾向, 与...

[Golang]Channel 特性整理-心智图

用途: sender和receiver沟通机制,FIFO(先进先出)。 参考来源: 郝林-Go语言核...

Day 4:认识HTML+HTML架构

建立HTML档案 打开VSCode > 档案 > 另存新档为html档+命名 > ...

Day8 - TextView(二)

上一篇把"Hello World!"更改成了了 但字体太小了,看不清楚到底打对还...