nodejs實現定時爬取微博熱搜

The summer is coming

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

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

廢話

我發現,本身對 coding 這件事的稱呼,從敲代碼 改成 寫代碼 了。前端

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

對,優雅!node

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

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

子非猿,安之 coding 之樂也。

看完本文的收穫

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

爲什麼寫爬蟲相關的文章

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

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

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

解決方案

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

都看到這裏啦,就很棒! 點個贊 再走嘛。
2020521522
程序員導航站:https://iiter.cn

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

代碼 github 已開源:

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

相關文章
相關標籤/搜索