前言
如果只是一次性的爬取数据,那我推荐直接在浏览器里跑js脚本就行,例子可看写爬虫?前端er何必用python
但如果你想定时自动爬取数据,这时js表示依然小case。当然,得用nodejs了,不多说,直接上需求:
需求:每天0点自动爬取获取豆瓣评分电影Top250的详细信息
每天0点获取豆瓣评分电影Top250的以下信息,并存到本地/数据库/..
前置知识
下面涉及一些第三方库,只需要简单了解即可使用,莫慌~
- 简单nodejs知识
- cheerio: jquery风格的解析和操作 html/xml 库
- node-schedule:可实现定时执行任务
nodejs实现
1. 分析页面请求,找到数据来源,分析接口规律
根据上一篇写爬虫?前端er何必用python知道:
- 第一页接口路径:movie.douban.com/top250?star...
- 第二页接口路径:movie.douban.com/top250?star...
依此类推,第几页只是start参数不一样
2. 用代码模拟获取接口数据
nodejs从18.x版本开始支持fetch接口,nodejs.org/en/blog/ann... ,所以用我们熟悉的fetch直接撸:
js
fetch("https://movie.douban.com/top250?start=1")
.then(function (response) {
return response.text();
})
.then((r) => {
console.log(333, r.slice(0, 1000)); // 用slice截部分用于演示
});
跑下,没问题
3. 过滤处理提取数据
处理数据就要找到数据规律,这一步是灵魂。简单看下发现每部电影信息都在 li 标签里了,只需把对应文本提取出来即可。
从html里过滤提取数据,用dom api操作是我们熟悉且方便的,不出意外的是,node端也有这个能力,下面通过cheerio 来实现,cheerio库是jquery(链式调用)风格的解析和操作 html/xml 库。简单熟悉下cheerio文档,搜下别人怎么用,直接撸:
js
const cheerio = require("cheerio");
// ...
console.log(333, r.slice(0, 1000)); // 用slice截部分用于演示
const $ = cheerio.load(r);
const itemList = $(".item").each((idx, el) => {
const item = cheerio.load($(el).prop("outerHTML"));
// 电影详情链接
const link = item("a").attr("href");
// 图片链接
const imageUrl = item("img").attr("src");
// 影片名字
const name = item(".title").prop("innerText");
// 评分
const rateNum = item(".rating_num").prop("innerText");
//评价数
const ratePerson = parseInt(item(".star > span:last-child").text());
// 概况
const about = item(".quote")?.prop("innerText");
// 相关信息
const desc = item(".bd > p").prop("innerText");
console.log(1111, { link, imageUrl, name, rateNum, ratePerson, about, desc });
});
// ...
跑下,没问题
4. 存储/下载/..数据
这里我选择保存为json文件,简单用fs、path写个工具函数:
js
const fs = require("fs");
const path = require("path");
// ...
const saveToJson = (filePath, content) => {
// 自动创建目录
const dirPath = path.dirname(filePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
fs.writeFile(filePath, content, function (err) {
if (err) {
console.error(err);
}
console.log(filePath, "写入成功!");
});
};
测试下,没问题!
5. 定时自动爬取
下面用node-schedule 来实现定时执行任务,简单写个demo测试下
js
const schedule = require("node-schedule");
// 当前时间的秒值为 10 时执行任务,如:2018-7-8 13:25:10
let job = schedule.scheduleJob("10 * * * * *", () => {
console.log(
"定时自动执行啦>>>",
new Date()
.toLocaleString()
.replace(", ", "_")
.replace(/\//g, "_")
.slice(0, 19)
);
});
其中时间数值按下表表示
markdown
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ |
│ │ │ │ │ └ 星期几,取值:0 - 7,其中 0 和 7 都表示是周日
│ │ │ │ └─── 月份,取值:1 - 12
│ │ │ └────── 日期,取值:1 - 31
│ │ └───────── 时,取值:0 - 23
│ └──────────── 分,取值:0 - 59
└─────────────── 秒,取值:0 - 59(可选)
跑下,没问题
全部代码
js
const cheerio = require("cheerio");
const fs = require("fs");
const path = require("path");
const schedule = require("node-schedule");
const top250 = {};
let currentPage = 1;
const spiderDoubanTop250 = async (url) => {
await fetch(url)
.then(function (response) {
return response.text();
})
.then((r) => {
const $ = cheerio.load(r);
$(".item").each((idx, el) => {
const item = cheerio.load($(el).prop("outerHTML"));
// 电影详情链接
const link = item("a").attr("href");
// 图片链接
const imageUrl = item("img").attr("src");
// 影片名字
const name = item(".title").prop("innerText");
// 评分
const rateNum = item(".rating_num").prop("innerText");
//评价数
const ratePerson = parseInt(item(".star > span:last-child").text());
// 概况
const about = item(".quote")?.prop("innerText");
// 相关信息
const desc = item(".bd > p").prop("innerText");
top250[name] = { link, imageUrl, name, rateNum, ratePerson, about, desc };
});
});
// 启动下一页
if (currentPage <= 10) {
currentPage++;
spiderDoubanTop250(
`https://movie.douban.com/top250?start=${25 * (currentPage - 1)}`
);
} else {
saveToJson(
`./doupanTop250/${new Date()
.toLocaleString()
.replace(", ", "_")
.replace(/\//g, "_")
.slice(0, 19)}.json`,
JSON.stringify(top250)
);
}
};
// 每天0:0:0点执行任务
schedule.scheduleJob("0 0 0 * * *", () => {
spiderDoubanTop250("https://movie.douban.com/top250?start=0");
});
const saveToJson = (filePath, content) => {
// 自动创建目录
const dirPath = path.dirname(filePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
fs.writeFile(filePath, content, function (err) {
if (err) {
console.error(err);
}
console.log("写入成功!");
});
};
总结
nodejs版的爬虫和浏览器js版的爬虫思路是一样的,借助fetch、cheerio提供的熟悉的接口,前端程序员可以复用经验,轻松上手nodejs爬虫。