其实这个小程序一个月前就已经做好了,但是当时忙着实习和毕设还有一些乱七八糟的事情,所以拖到现在才来做总结。
在春节的时候,疫情地图一开始用的比较多的是丁香医生的疫情地图,我们可以利用爬虫把这些数据获取下来。
我们发现一些数据图片并不是动态生成的,而是直接放回数据。
除此之外都是一些图片和JS文件了,这些页面的数据好像都是服务器渲染的。
我们通过搜索http
直接去查看页面的请求地址,然后再使用postman
去请求看看返回的数据是什么
比如:
1. svg格式
2. 一些连接地址
3. 结合前面的地图数据,这个应该是地图SVG的JSON文件
发现并没有疑似请求数据的接口。就在我不知道怎么进行下去的时候,发现了URL网址有线索。
由于我是从小程序公众号点击复制到PC端上打开的,所以发现url网址携带了一些参数。
https://ncov.dxy.cn/ncovh5/view/pneumonia_peopleapp?from=timeline&isappinstalled=0&scene=126&clicktime=1580901335
我们都知道url网址请求,其实是一个get
请求,携带的参数有一个clicktime
点击时间的描述,后面接的应该是Date.parse()返回的毫秒数。
所以我就修改这个时间,在postman
上去请求发现了一个突破口。
返回的是页面信息,而在标签里发现了携带了每一个地区的疫情数据。
我们就可以通过修改clicktime
去获取最新的疫情数据。
从这里也可以发现,丁香医生这个有可能使用了服务器渲染(我也不是很确定)。
既然处理的数据是html文件,就可以使用cheerio
进行处理数据,使用superagent
进行请求。
为了方便预览和调试,我使用了express
去搭建一个简单的服务器,把爬下来的结果渲染到页面上去查看。
const express = require("express");
const app = express();
let server = app.listen(3000, function() {
let host = server.address().address;
let port = server.address().port;
console.log("Your App is running at http://%s:%s", host, port);
});
app.get("/", async (req, res) => {
let result = await httpGet(); // 爬虫获取数据
res.send(handleDate(result)); // 展示到页面
});
这里主要是httpGet
这个函数,去爬取网页的数据。
在使用handleDate
去处理数据。
const cheerio = require("cheerio");// 处理页面的Node元素
const superagent = require("superagent");// 请求
function httpGet() {
return new Promise((res, rej) => {
superagent
.get(
`https://ncov.dxy.cn/ncovh5/view/pneumonia_peopleapp?from=timeline&isappinstalled=0&scene=126&clicktime=${Date.parse(
new Date()
)}`// 请求网页数据
)
.end((_err, _res) => {
if (_err) {
// 如果访问失败或者出错,会这行这里
console.log(`爬取出错 - ${_err}`);
rej(_err);
} else {
return res(_res);
}
});
});
}
很明显是一个JSON格式的对象,这个对象中的text
属性保存着这个页面的html
格式,我们输出看看。
浏览器会自动解析html文件,所以这是一个格式正确的html文件。
并且发现script
标签保存了疫情的详细数据
script
标签也是node元素,所以可以使用cheerio
去解析获取里面的数据。
每一个script
标签都有一个id
属性,我们可以通过这个去定位获取对应的数据
发现id为getAreaStat
是各省各市的数据,我们来获取看看。
function handleDate(res) {
res = cheerio("script", res.text);
let reslut = "";//保存最后的结果
for (let [key, value] of Object.entries(res)) {
// console.log( typeof value.children, isNaN(key))
if (isData(key, value) && /getAreaStat/g.test(value.children[0].data)) {
reslut = value.children[0].data.slice(26, -11);//是数据变成json格式
}
}
return result
}
我们看看最后的结果是什么
这个是JSON格式的对象,我们只有按照我们想要的格式去处理这些数据即可。
除此之外还有很多数据可以去获取,比如国外的数据,全国各省的数据,每天的数据变化等,只需要修改检测的script的id值
就可以。
nodejs
本身就有一个fs
库操作本地文件,所以我们直接使用就可以。
剩下工作就是调用API了。
/**
* @param {请求的数据} object reslut
* @param {文件名} string filename
*/
let defaultData = require("./defaultData");
function writeDate(reslut, filename) {
let date = new Date();
let r = defaultData;// 我们需要保存的格式
// 文件名
let str = `./home/${filename} ${date.getMonth() +
1}-${date.getDate()} ${date.getHours()} ${date.getMinutes()} ${date.getMilliseconds()}.json`;
for (let item of reslut) {
// 将每一个省份保存到对应的属性下
let name = getCityName(item.provinceShortName);
if (name) {
r.data[0][name] = JSON.stringify(item);
}
}
// 设置数据库id 和创建时间
r.data[0]["id"] = new objecId().toString();
r.data[0]["created_at"] = Date.parse(date);
r.data[0]["updated_at"] = Date.parse(date);
// 写入文件
fs.writeFileSync(str, JSON.stringify(r));
}
由于我们每天需要最新的文件格式,所以我们每次更新需要把之前的文件删除,我们同样使用fs
API即可。
// 清空文件夹
// path 是文件夹路径
function delDir(path){
let files = [];
if(fs.existsSync(path)){
files = fs.readdirSync(path);
files.forEach((file, index) => {
let curPath = path + "/" + file;
if(fs.statSync(curPath).isDirectory()){
delDir(curPath); //递归删除文件夹
} else {
fs.unlinkSync(curPath); //删除文件
}
});
}
}