nodejs實現定時爬取微博熱搜

程序員導航站:https://iiter.cn

The summer is coming

我知道,那些夏天,就像青春一樣回不來。 - 宋冬野

青春是回不來了,倒是要準備渡過在西安的第三個夏天了。

廢話

我發現,自己對 coding 這件事的稱呼,從敲代碼 改爲 寫代碼 了。

emmm…敲代碼,自我感覺,就像是,習慣了用 const 定義常量的我看到別人用 var 定義的常量。

對,優雅!

寫代碼 這三個字,顯得更爲優雅一些,更像是在創作,打磨一件精緻的作品。

改編自 掘金站長 的一句話:

子非猿,安之 coding 之樂也。

看完本文的收穫

  • ctrl + c
  • ctrl + v
  • nodejs 入門級爬蟲

爲何寫爬蟲相關的文章

最近訪問 艾特網 的時候發現請求有點慢。

後來經過一番檢查,發現首頁中搜索熱點需要每次去爬取百度熱搜的數據並當做接口返回給前端,由於是服務端渲染,接口堵塞就容易出現訪問較慢的情況。

就想着對這個接口進行一次重構。

解決方案

  • 設置定時任務,每隔1分鐘/3分鐘/5分鐘爬取新浪微博實時熱搜(新浪微博熱搜點擊率更高一些)
  • 爬取到數據後不直接返回給前端,先寫入一個.json格式的文件。
  • 服務端渲染的後臺接口請求並返回給前端json文件的內容

需求捋清楚以後就可以開幹了。

創建工程

初始化

首先得找到目標站點,如下:(微博實時熱搜)

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

創建文件夾 weibo

進入文件夾根目錄

使用 npm init -y 快速初始化一個項目

安裝依賴

創建app.js文件

安裝以下依賴

npm i cherrio superagent -D

關於superagentcherrio的介紹

superagent 是一個輕量級、漸進式的請求庫,內部依賴 nodejs 原生的請求 api,適用於 nodejs 環境。

cherrio 是 nodejs 的抓取頁面模塊,爲服務器特別定製的,快速、靈活、實施的 jQuery 核心實現。適合各種 Web 爬蟲程序。node.js 版的 jQuery。

代碼編寫

打開 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 元素進行一波分析。
202051225827
jQuery 比較熟的小老弟,看到下圖如此簡潔清晰明瞭的 DOM 結構,是不是有 N 種取出它每個 tr 中的數據並 push 到一個 Array 裏的方法呢?
202051224321
對!我們最終的目的就是要通過 jQuery 的語法,遍歷每個 tr ,並將其每一項的 熱搜地址熱搜內容熱度值序號表情等信息 push 進一個空數組中
再將它通過 nodejsfs 模塊,寫入一個 json 文件中。
202051224844

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 文件。

但是,每次都要手動運行,才能爬取到當前時間段的熱搜數據,這一點都 不人性化!

最近微博熱搜瓜這麼多,咱可是一秒鐘可都不能耽擱。我們最開始期望的是每隔多長時間 定時執行爬取 操作。瓜可不能停!

202052144841
接下來,對代碼進行 小部分改造

數據請求封裝

由於 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);
  }
});

各種玩法

  • 以上代碼可直接集成進現有的 express koa eggjs 或者原生的 nodejs 項目中,作爲接口返回給前端。

  • 集成進 Serverless,作爲接口返回給前端。

  • 對接微信公衆號,發送 熱搜 關鍵字即可實時獲取熱搜數據。

  • 集成進 微信機器人 ,每天在指定的時間給自己/羣裏發送微博熱搜數據。

  • other…

都看到這裏啦,就很棒! 點個贊 再走嘛。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hY4XC6T8-1588426974146)(https://static.iiter.cn/article/2020521522.png)]
程序員導航站:https://iiter.cn

下面是咱的公衆號呀 前端糖果屋
202052154453

代碼 github 已開源:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章