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
關於superagent
和cherrio
的介紹
”superagent 是一個輕量級、漸進式的請求庫,內部依賴 nodejs 原生的請求 api,適用於 nodejs 環境。
”cherrio 是 nodejs 的抓取頁面模塊,為服務器特別定制的,快速、靈活、實施的 jQuery 核心實現。適合各種 Web 爬蟲程序。node.js 版的 jQuery。
代碼編寫
打開 app.js
,開始完成主要功能
首先在頂部引入cheerio
、superagent
以及 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
請求 superagent
的 get
方法接收兩個參數。第一個是請求的 url
地址,第二個是請求成功后的回調函數。
回調函數有倆參數,第一個參數為 error
,如果請求成功,則返回 null
,反之則拋出錯誤。第二個參數是請求成功后的 響應體
superagent.get(hotSearchURL, (err, res) => {
if (err) console.error(err);
});
網頁元素分析
打開目標站對網頁中的 DOM
元素進行一波分析。 對
jQuery
比較熟的小老弟,看到下圖如此簡潔清晰明了的 DOM
結構,是不是有 N 種取出它每個 tr
中的數據並 push
到一個 Array
里的方法呢? 對!我們最終的目的就是要通過
jQuery
的語法,遍歷每個 tr
,並將其每一項的 熱搜地址
、熱搜內容
、 熱度值
、序號
、表情
等信息 push
進一個空數組中
再將它通過 nodejs
的 fs
模塊,寫入一個 json
文件中。
jQuery 遍歷拿出數據
使用 jQuery
的 each
方法,對 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
語法,還得將請求成功后返回的響應體,用 cheerio
的 load
方法進行包裝。
const $ = cheerio.load(res.text);
寫入 json 文件
接着使用 nodejs
的 fs
模塊,將創建好的數組轉成 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 文件。
但是,每次都要手動運行,才能爬取到當前時間段的熱搜數據,這一點都 不人性化!
最近微博熱搜瓜這么多,咱可是一秒鍾可都不能耽擱。我們最開始期望的是每隔多長時間 定時執行爬取
操作。瓜可不能停!
接下來,對代碼進行
小部分改造
。
數據請求封裝
由於 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......
都看到這里啦,就很棒! 點個贊
再走嘛。
程序員導航站:https://iiter.cn
下面是咱的公眾號呀 前端糖果屋
代碼 github 已開源: