• 为何写爬虫相关的文章

    最近访问 艾特网 的时候发现请求有点慢。

    后来经过一番检查,发现首页中搜索热点需要每次去爬取百度热搜的数据并当做接口返回给前端,由于是服务端渲染,接口堵塞就容易出现访问较慢的情况。

    就想着对这个接口进行一次重构。

    解决方案

    需求捋清楚以后就可以开干了。

    创建工程

    初始化

    首先得找到目标站点,如下:(微博实时热搜)

    https://s.weibo.com/top/summary?cate=realtimehot

    创建文件夹 weibo

    进入文件夹根目录

    使用 npm init -y 快速初始化一个项目

    安装依赖

    创建app.js文件

    安装以下依赖

    npm i cherrio superagent -D

    关于superagentcherrio的介绍

    代码编写

    打开 app.js ,开始完成主要功能

    首先在顶部引入cheeriosuperagent 以及 nodejs 中的 fs 模块

    const cheerio = require("cheerio");
    const superagent = require("superagent");
    const fs = require("fs");

    通过变量的方式声明热搜的url,便于后面 复用

    const weiboURL = "https://s.weibo.com";
    const hotSearchURL = weiboURL + "/top/summary?cate=realtimehot";

    superagent

    使用 superagent 发送get请求
    superagentget 方法接收两个参数。第一个是请求的 url 地址,第二个是请求成功后的回调函数。

    回调函数有俩参数,第一个参数为 error ,如果请求成功,则返回 null,反之则抛出错误。第二个参数是请求成功后的 响应体

    superagent.get(hotSearchURL, (err, res) => {
    if (err) console.error(err);
    });

    网页元素分析

    打开目标站对网页中的 DOM 元素进行一波分析。
    nodejs实现定时爬取微博热搜-LMLPHP
    jQuery 比较熟的小老弟,看到下图如此简洁清晰明了的 DOM 结构,是不是有 N 种取出它每个 tr 中的数据并 push 到一个 Array 里的方法呢?
    nodejs实现定时爬取微博热搜-LMLPHP
    对!我们最终的目的就是要通过 jQuery 的语法,遍历每个 tr ,并将其每一项的 热搜地址热搜内容热度值序号表情等信息 push 进一个空数组中

    再将它通过 nodejsfs 模块,写入一个 json 文件中。
    nodejs实现定时爬取微博热搜-LMLPHP

    jQuery 遍历拿出数据

    使用 jQueryeach 方法,对 tbody 中的每一项 tr 进行遍历,回调参数中第一个参数为遍历的下标 index,第二个参数为当前遍历的元素,一般 $(this) 指向的就是当前遍历的元素。

    let hotList = [];
    $("#pl_top_realtimehot table tbody tr").each(function (index) {
    if (index !== 0) {
    const $td = $(this).children().eq(1);
    const link = weiboURL + $td.find("a").attr("href");
    const text = $td.find("a").text();
    const hotValue = $td.find("span").text();
    const icon = $td.find("img").attr("src")
    ? "https:" + $td.find("img").attr("src")
    : "";
    hotList.push({
    index,
    link,
    text,
    hotValue,
    icon,
    });
    }
    });

    cheerio 包装请求后的响应体

    nodejs 中,要想向上面那样愉快的写 jQuery 语法,还得将请求成功后返回的响应体,用 cheerioload 方法进行包装。

    const $ = cheerio.load(res.text);

    写入 json 文件

    接着使用 nodejsfs 模块,将创建好的数组转成 json字符串,最后写入当前文件目录下的 hotSearch.json 文件中(无此文件则会自动创建)。

    fs.writeFileSync(
    `${__dirname}/hotSearch.json`,
    JSON.stringify(hotList),
    "utf-8"
    );

    完整代码如下:

    const cheerio = require("cheerio");
    const superagent = require("superagent");
    const fs = require("fs");
    const weiboURL = "https://s.weibo.com";
    const hotSearchURL = weiboURL + "/top/summary?cate=realtimehot";
    superagent.get(hotSearchURL, (err, res) => {
    if (err) console.error(err);
    const $ = cheerio.load(res.text);
    let hotList = [];
    $("#pl_top_realtimehot table tbody tr").each(function (index) {
    if (index !== 0) {
    const $td = $(this).children().eq(1);
    const link = weiboURL + $td.find("a").attr("href");
    const text = $td.find("a").text();
    const hotValue = $td.find("span").text();
    const icon = $td.find("img").attr("src")
    ? "https:" + $td.find("img").attr("src")
    : "";
    hotList.push({
    index,
    link,
    text,
    hotValue,
    icon,
    });
    }
    });
    fs.writeFileSync(
    `${__dirname}/hotSearch.json`,
    JSON.stringify(hotList),
    "utf-8"
    );
    });

    打开终端,输入 node app,可看到根目录下多了个 hotSearch.json 文件。

    定时爬取

    虽然代码可以运行,也能爬取到数据并存入 json 文件。

    但是,每次都要手动运行,才能爬取到当前时间段的热搜数据,这一点都 不人性化!

    最近微博热搜瓜这么多,咱可是一秒钟可都不能耽搁。我们最开始期望的是每隔多长时间 定时执行爬取 操作。瓜可不能停!

    nodejs实现定时爬取微博热搜-LMLPHP
    接下来,对代码进行 小部分改造

    数据请求封装

    由于 superagent 请求是个异步方法,我们可以将整个请求方法用 Promise 封装起来,然后 每隔指定时间 调用此方法即可。

    function getHotSearchList() {
    return new Promise((resolve, reject) => {
    superagent.get(hotSearchURL, (err, res) => {
    if (err) reject("request error");
    const $ = cheerio.load(res.text);
    let hotList = [];
    $("#pl_top_realtimehot table tbody tr").each(function (index) {
    if (index !== 0) {
    const $td = $(this).children().eq(1);
    const link = weiboURL + $td.find("a").attr("href");
    const text = $td.find("a").text();
    const hotValue = $td.find("span").text();
    const icon = $td.find("img").attr("src")
    ? "https:" + $td.find("img").attr("src")
    : "";
    hotList.push({
    index,
    link,
    text,
    hotValue,
    icon,
    });
    }
    });
    hotList.length ? resolve(hotList) : reject("errer");
    });
    });
    }

    node-schedule 详解

    定时任务我们可以使用 node-schedule 这个 nodejs库 来完成。

    https://github.com/node-schedule/node-schedule

    先安装

    npm i node-schedule -D

    头部引入

    const nodeSchedule = require("node-schedule");

    用法(每分钟的第 30 秒定时执行一次):

    const rule = "30 * * * * *";
    schedule.scheduleJob(rule, () => {
    console.log(new Date());
    });

    规则参数:

    *    *    *    *    *    *
    ┬ ┬ ┬ ┬ ┬ ┬
    │ │ │ │ │ │
    │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
    │ │ │ │ └───── month (1 - 12)
    │ │ │ └────────── day of month (1 - 31)
    │ │ └─────────────── hour (0 - 23)
    │ └──────────────────── minute (0 - 59)
    └───────────────────────── second (0 - 59, OPTIONAL)

    6 个占位符从左到右依次代表:秒、分、时、日、月、周几
    * 表示通配符,匹配任意。当 * 为秒时,表示任意秒都会触发,其他类推。
    来看一个 每小时的第20分钟20秒 定时执行的规则:

    20 20 * * * *

    更多规则自行搭配。

    定时爬取,写入文件

    使用定时任务来执行上面的请求数据,写入文件操作:

    nodeSchedule.scheduleJob("30 * * * * *", async function () {
    try {
    const hotList = await getHotSearchList();
    await fs.writeFileSync(
    `${__dirname}/hotSearch.json`,
    JSON.stringify(hotList),
    "utf-8"
    );
    } catch (error) {
    console.error(error);
    }
    });

    哦对,别忘了 捕获异常

    下面贴上完整代码(可直接 ctrl c/v):

    const cheerio = require("cheerio");
    const superagent = require("superagent");
    const fs = require("fs");
    const nodeSchedule = require("node-schedule");
    const weiboURL = "https://s.weibo.com";
    const hotSearchURL = weiboURL + "/top/summary?cate=realtimehot";
    function getHotSearchList() {
    return new Promise((resolve, reject) => {
    superagent.get(hotSearchURL, (err, res) => {
    if (err) reject("request error");
    const $ = cheerio.load(res.text);
    let hotList = [];
    $("#pl_top_realtimehot table tbody tr").each(function (index) {
    if (index !== 0) {
    const $td = $(this).children().eq(1);
    const link = weiboURL + $td.find("a").attr("href");
    const text = $td.find("a").text();
    const hotValue = $td.find("span").text();
    const icon = $td.find("img").attr("src")
    ? "https:" + $td.find("img").attr("src")
    : "";
    hotList.push({
    index,
    link,
    text,
    hotValue,
    icon,
    });
    }
    });
    hotList.length ? resolve(hotList) : reject("errer");
    });
    });
    }
    nodeSchedule.scheduleJob("30 * * * * *", async function () {
    try {
    const hotList = await getHotSearchList();
    await fs.writeFileSync(
    `${__dirname}/hotSearch.json`,
    JSON.stringify(hotList),
    "utf-8"
    );
    } catch (error) {
    console.error(error);
    }
    });

    各种玩法

    都看到这里啦,就很棒! 点个赞 再走嘛。
    nodejs实现定时爬取微博热搜-LMLPHP

    程序员导航站:https://iiter.cn

    下面是咱的公众号呀 前端糖果屋
    nodejs实现定时爬取微博热搜-LMLPHP

    代码 github 已开源:

    https://github.com/isnl/weibo-hotSearch-crawler

    05-28 12:27