本项目由本人全程独立开发,转载请注明出处
爬取学校数字平台仅做前后端交互项目练习,并不对学校信息安全造成隐患
阅读本文,具备node.js,express,puppeteer基础最佳
github传送门:https://github.com/Escaay/Scrape-Student-Info
前端(1为成功,0为错误)
后端(1为成功,0为错误)
演示
前端页面只说一下js实现渐入,css设置opacity=0,再用js定时器逐加opacity到1,值得注意的是
//内容渐入
let display = () => {
//此处注意opacity原值是字符串类型,需要将它转变为number类型再赋值
item.style.opacity = 0.1 + Number(item.style.opacity)
if (item.style.opacity == 1) {
clearInterval(time)
}
}
let time = setInterval(() => {
display()
}, 100)
看到上面的注释了吗,不要掉进隐式类型转换的坑(确实把我坑了)
1.关于验证码图片抓取
两种方式,一种是用node fs下载验证码图片,但是你会发现当你抓取图片的时候,验证码图片就会自动刷新一次,一开始我以为它是100张随机图,后来发现即使是随机数一样每一次显示的验证码图片也不一样,这条路不好走
换一种思维,这个时候puppeteer的强大功能就体现出来了,screenshot对验证码进行截图保存到本地,如此一来验证码图片就不会刷新了,接下来调用百度ai识别图片的接口识别图片,具体实现可以参考百度ai的文档
验证码传回来之后,我们对验证码作去除空白的处理,代码如下
await getToken().then(async(code) => {
//点击验证码输入框
await page.click('#imageCodeName');
// 用trim方法去除code中的两端空白,再用正则去除中间空格,因为验证码限制只能输入四位
await page.keyboard.type((code.trim()).replace(/\s/g, ''));
})
因为ai有时候会识别错误,正确率在百分之八十左右,而且密码错误,函数并不会抛出错误,所以我们要主动判断是否登录成功 ,如果失败则抛出错误重新抓取验证码尝试,代码如下
try {
await page.click('#fm1 > table > tbody > tr:nth-child(5) > td > input[type=submit]')
//等待一秒防止误判failLogin仍然存在,此处有待优化
await page.waitForTimeout(1000)
// 验证码错误不会抛出错误,所以需要判断登录是否成功,如果失败,抛出错误触发catch
let failLogin = await page.$('#fm1 > table > tbody > tr:nth-child(5) > td > input[type=submit]')
// console.log(failLogin); //如果验证码识别正确,登录成功则为null
if (failLogin) throw '验证码错误,正在重试'
} catch {
//重新登录
await Login()
}
注意try和catch必须包含在login函数中,这样才能达到无限自动重试的效果
2.获取消费记录数据
当我们登录成功来到消费记录页面,我们发现这是一个表格,现在我们需要把里面的数据全部抓取出来包装成一个二维数组返回给客户端
注意在async函数里面使用forEach会导致forEach中无法使用await,因为有块级作用域,有两种解决方案,一种是重写Array的forEach,一种是老老实实用for循环,我们这里用for循环做演示,代码如下
const Data = await page.evaluate(() => {
let thdata = []
let tddata = []
const ths = document.querySelectorAll('th')
const tds = document.querySelectorAll('.xfmx-table td')
const rows = 0
for (var th of ths) {
thdata.push(th.innerText)
}
for (var td of tds) {
tddata.push(td.innerText)
}
return { thdata, tddata }
})
let thdata = Data.thdata
let tddata = Data.tddata
let consume = []
for (let p = 0; p < tddata.length; p = p + 6) {
consume.push(tddata.slice(p, p + 6))
}
3.配置跨域
项目是在本地测试,用express搭建服务后在localhost调用接口需要配置跨域
代码如下
let port = 3000
app.get('/', (req, res) => {
let uname = url.parse(req.url, true).query.uname
let upwd = url.parse(req.url, true).query.upwd
//配置跨域
res.header("Access-Control-Allow-Origin", "*");
scrape(uname, upwd).then((value) => { res.send(value) })
})
app.listen(port, () => { console.log(`express is run in ${port}`); })
关于其他的细节可以参考github上的代码,如果有可以优化的地方欢迎指出----Aays