整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

简单实用node脚本!通过定时任务和2个fetch请求实现网站自动签到

一个网站每天签到可以获取流量,之前每天都是自己打开网页登录然后手动点签到,但是如果连续7天没签到之前获取的所有流量都会清空。类似的需求会有很多,很多网站、应用也都会提供签到获取积分、金币、能量、饲料...其实用node脚本来实现每天自动签到很简单。

想到的实现方案

  • 用无头浏览器 Puppeteer、Playwright 之类的库模拟dom操作,这个稍微复杂点,涉及到比较离谱的图形验证还需要接入第三方验证码识别
  • 抓包应用的网络请求,直接通过分析登录、签到接口,发起网络请求实现签到

下方示例采用了第二种接口签到方式,一般我们只需要先分析登录接口,拿到登录态,如果是直接返回的 token 那就更简单了,也有的网站会自动通过 cookie 设置登录态,拿到登录态我们再带上登录态去请求签到接口就可以了。

实现步骤

1、分析登录接口,拿到登录态参数

输入账号密码登录后,发现登录接口 Response 里并没有数据返回,然后看 Headers 里的 Response Headers 里的 Set-Cookies 就是登陆成功服务端自动设置的登录态信息:

添加图片注释,不超过 140 字(可选)

这一步只需要带着账号密码参数去请求登录接口,然后再解析出 Set-Cookies 里我们需要的参数就行了,发起请求不需要用第三方库,17.5.0版本后 node 里也可以直接使用 fetch 发送请求了,可以直接在浏览器控制台 network 里选中接口右键 Copy - Copy as Node.js fetch 复制,示例代码如下

/**
 * 获取登录态 cookies
 * @returns
 */
const getCookie = async function() {
    const res = await fetch("https://cafe123.cn/auth/login", {
        "headers": {
            "accept": "application/json, text/javascript, */*; q=0.01",
            "accept-language": "zh-CN,zh;q=0.9",
            "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
            "sec-ch-ua": "\"Google Chrome\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"",
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": "\"Windows\"",
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "same-origin",
            "x-requested-with": "XMLHttpRequest",
            "Referer": "https://cafe123.cn/auth/login",
            "Referrer-Policy": "strict-origin-when-cross-origin"
        },
        "body": "email=123456%40qq.com&passwd=123456&code=&remember_me=week",
        "method": "POST"
    })

    if (res.status !== 200) {
        console.log('登录失败:', new Date(), res)
        return
    }

    // res.json().then(r => {
    //   console.log(r)
    // })
    const headers = res.headers
    cookies = []
    for (const element of headers) {
        if (element[0] === 'set-cookie') {
            const val = element[1].split(';')[0]
            cookies.push(val)
        }
    }
    checkin()
}

拿到响应结果后,通过 res.headers 可以拿到响应头里的所有参数,然后可以用 for of 遍历下取出我们需要的参数就行了。

发起签到请求

先按照上面登录接口类似的方法分析签到接口的构成,这一步主要是请求头里面的 cookie 参数,直接用上一步拿到的响应头里的 Set-Cookies 里的登录信息按 key=value 用分号分隔拼接好就行了。

/**
 * 签到
 */
const checkin = async function() {
    if (!cookies) {
        getCookie()
        return
    }

    const res = await fetch("https://cafe123.cn/user/checkin", {
        "headers": {
            "accept": "application/json, text/javascript, */*; q=0.01",
            "accept-language": "zh-CN,zh;q=0.9",
            "sec-ch-ua": "\"Google Chrome\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"",
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": "\"Windows\"",
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "same-origin",
            "x-requested-with": "XMLHttpRequest",
            "cookie": cookies.join(';'),
            "Referer": "https://cafe123.cn/user",
            "Referrer-Policy": "strict-origin-when-cross-origin"
        },
        "body": null,
        "method": "POST"
    })
    if (res.status !== 200) {
        console.log('签到失败:', new Date().toLocaleString(), res)
        cookies = null
        getCookie()
    } else {
        console.log('签到成功:', new Date().toLocaleString(), res)
        res.json().then(r => {
            console.log(r)
        })
    }
}

3、发起签到请求

这一步需要借助定时任务 node-cron 库,注意 node-cron 表达式总共6位,首位的秒是可以省略的,所以也可以5位:

/**
 * 定时任务
 */
cron.schedule("30 2 8 * * *", function() {
    // node-cron 表达式总共6位,首位的秒可以省略,所以也可以5位
    // 每秒执行一次 * * * * * *
    // 每天12点30分执行一次  30 12 * * *
    // 每隔5秒执行一次  */5 * * * * *
    // 每天早上8点到晚上6点之间每个正点执行任务一次  0 0 8-18 * * *,等同于  0 0 8,9,10,11,12,13,14,15,16,17,18 * * *
    console.log("触发定时任务:", new Date().toLocaleString())
    checkin()
})

注意问题

如果在自己本地电脑上运行,windows 进入睡眠状态时,定时任务是不会执行的,即使用 pm2 启动服务也不会执行的,只能设置让电脑从不睡眠了,或者有服务器的部署在自己的服务器上跑。

还有如果想要在每天随机一个时间执行内执行,用 cron 表达式是不行的,定时任务开启后只能在某个时间点执行或者间隔多长时间执行,想到的方案是可以开启两个定时任务,第一个都是每天同一时间触发,最好是每天0点0分0秒,然后在这个定时任务里再去随机获取一个时间,去开启另一个定时任务去执行,这个我还没实验,或者你有更好的方案可以分享下哟!

完整的示例项目源码地址:https://github.com/cafehaus/check-in,直接把里面的接口地址、参数这些换一下就行了。

篇文章讲到HTML炫酷的主流框架,今天小明就介绍一个HTML5功能的实现代码

Introduce(介绍)

用户签到的H5例子(css+jquery,无图片),由于网上找的的用户签到例子都不好,要不就是好多图片组成的,要不就大量冗余代码,所以特意做了个签到界面(移动端)。

User sign sample page for mobile using h5 which only use css + jquery + html.

一些关键的地方

这个功能的编写思路是,先构建日期和签到相关数据,然后从服务端获取数据,并对原有数据进行更改,最后进行渲染。

这样子很好的摆脱了逻辑比较凌乱的问题,并且可以直接将这些数据用 vue.js 来挂载(本文没有这样做)。

生成日期数据

//生成日期数据
 function buildData() {
 var da = {
 dates: [],//日期数据,从1号开始
 current: '',//当前日期
 monthFirst: 1,//获取当月的1日等于星期几
 month: 0,//当前月份
 days: 30,//当前月份共有多少天
 day: 0,//今天几号了
 isSigned: false,//今天是否已经签到
 signLastDays:3,//连续签到日子
 signToday: function () {
 this.isSigned = true;
 this.dates[this.day].isSigned = true;
 },
 };
 var ds = [];
 //初始化日期数据
 var dt = new Date();
 da.current = dt.ToString('yyyy年M月d日');
 da.monthFirst = new Date(dt.getFullYear(), dt.getMonth(), 1).getDay();
 da.month = dt.getMonth() + 1;
 da.days = new Date(dt.getFullYear(), parseInt(da.month), 0).getDate();//获取当前月的天数
 da.day = dt.getDate();
 for (var i = 1; i < da.days + 1; i++) {
 var o = {
 isSigned: false,//是否签到了
 num: i,//日期
 isToday: i == da.day,//是否今天
 isPass: i < da.day,//时间已过去
 };
 ds[i] = o;
 }
 da.dates = ds;
 return da;
 }

有了数据之后,就可以将数据转换为界面了

//渲染数据
 function renderData(da) {
 var signDays = document.getElementById('spSignDays');
 signDays.innerText = da.signLastDays;
 var root = document.getElementById("signTable");
 root.innerHTML = '';
 var tr, td;
 var st = da.monthFirst;
 var dates = da.dates;
 var rowcount = 0;
 //最多6行
 for (var i = 0; i < 42; i++) {
 if (i % 7 == 0) {
 //如果没有日期了,中断
 if (i > (st + da.days))
 break;
 tr = document.createElement('tr');
 tr.className = 'darkcolor trb';
 root.appendChild(tr);
 rowcount++;
 }
 //前面或后面的空白
 if (i < st || !dates[i - st + 1]) {
 td = document.createElement('td');
 td.innerHTML = '<div class="sign-blank"><span></span></div>';
 tr.appendChild(td);
 continue;
 }
 //填充数字部分
 var d = dates[i - st + 1];
 td = document.createElement('td');
 var tdcss = '';
 if (d.isToday)
 tdcss = 'sign-today';
 else if (d.isPass)
 tdcss = 'sign-pass';
 else
 tdcss = 'sign-future';
 if (d.isSigned) {
 tdcss = 'sign-signed ' + tdcss;
 td.innerHTML = '<div class="' + tdcss + '"><span>' + d.num + '</span><svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="sign-pin svg-triangle "><polygon points="0,0 35,0 0,35" /></svg></div>';
 } else {
 tdcss = 'sign-unsign ' + tdcss;
 td.innerHTML = '<div class="' + tdcss + '"><span>' + d.num + '</span></div>';
 }
 tr.appendChild(td);
 }
 //计算是否需要添加最后一行
 if ((st + da.days + 1) / 7 > rowcount)
 root.appendChild(tr);
 }
 //构建日期数据
 var da = buildData();
 //渲染
 renderData(da);

以上就是本篇文章的全内容了

学习从来不是一个人的事情,要有个相互监督的伙伴,想要学习或交流前端问题的小伙伴可以私信回复小明“学习” 获取资料,一起学习!

到功能

1、数据库中要有相应的表,并创建相应的实体类,复写相关方法

2、在相关的jsp页面添加两个jsp按钮,用于签到与签退,并添加id属性

签到签退

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<base href="<%=basePath%>" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<link href="css/style.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript">
//给按钮signin绑定单击事件,实现签到功能
$(function(){
$("#signin").click(function(){
//alert("ok");
//发送ajax请求,完成签到.并通过回调函数显示结果
$.ajax({
url:"duty?method=signin",
type:"post",
dataType:"text",
success:function(data){
//显示签到结果
if(data==0){
$("#result").html("签到失败");
}else if (data==1) {
$("#result").html("签到成功");
}else {
$("#result").html("已经签到,无需重复签到");
}
}
});
});

//给按钮signout绑定单击事件,实现签退
$("#signout").click(function(){
//alert("ok?");
$.ajax({
url:"duty?method=signout",
type:"post",
dataType:"text",
success:function(result){
$("#result").html(result);
}
});
});

});



</script>
</head>

<body>

<div class="place">
<span>位置:</span>
<ul class="placeul">
<li><a href="#">考勤管理</a></li>
<li><a href="#">签到签退</a></li>
</ul>
</div>

<div class="formbody">

<div class="formtitle"><span>基本信息</span></div>

<ul class="forminfo">
<li><label> </label><input name="signinTime" type="button" class="btn" id="signin" value="签到"/> 每天签到一次,不可重复签到</li>
<li><label> </label></li>
<li><label> </label></li>
<li><label> </label><input name="signoutTime" type="button" class="btn" id="signout" value="签退"/>可重复签退,以最后一次签退为准</li>
</ul>

</div>
<div id="result"></div>

</body>

</html>

3、在servlet中编写签到相关方法

servlet方法

//考勤签到操作
public void signin(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

//获取session作用域中的emp对象,emp存放的是被考勤人信息,自行创建
Employee emp= (Employee) request.getSession().getAttribute("emp");
String empId=emp.getEmpId();
//调用业务层完成签到操作
DutyService ds=new DutyServiceImpl();
int n=ds.signin(empId);
//0失败 1成功 2已经签到

//无需页面跳转,直接响应结果(通常与ajax连用)
response.getWriter().println(n);
}
4、在service实现类编写签到的逻辑方法

业务实现类

截图勾线有误,其实进行在dao层进行了两个操作 1、查询是否签到的操作 2、保存签到信息的操作

@Override
 public int signin(String empId) {
  //判断用户是否已经签到
  Date now=new Date();//yyyyMMdd——>hhmmss
  java.sql.Date today=new java.sql.Date(now.getTime());//hhmmss
  boolean flag= dd.find(empId,today);//1、查询是否签到的操作
  
  //对返回的flag进行处理,true:已经签到,false没有签到,进行签到
  int n=0;
  if (flag) {
   return 2;
  } else {
   Duty duty=new Duty();
   //duty.setDtId(dtId);序列自增
   duty.setEmpId(empId);
   duty.setDtDate(today);
   DateFormat sdf = new SimpleDateFormat("hh:mm:ss");
   String signinTime= sdf.format(now);
   duty.setSigninTime(signinTime);
   
   //完成签到
   n=dd.save(duty);//2、保存签到信息的操作
   return n;//0失败 1成功
  }
 }

5、dao层实现类

dao层方法

@Override
public boolean find(String empId, Date today) {
//jdbc查询操作
//创建jdbc变量
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
//创建变量
boolean flag=false;
//默认情况为查不到
try {
//创建连接
conn=DBUtil.getConnection();
//创建SQL语句
String sql="select * from duty where empid=? and dtdate=?";
//创建SQL命令
ps=conn.prepareStatement(sql);
//给占位符赋值
ps.setString(1, empId);
ps.setDate(2, today);
//执行sql语句
rs=ps.executeQuery();
//遍历
if (rs.next()) {
flag=true;
//签到成功
}

} catch (Exception e) {
e.printStackTrace();
}finally{
//关闭变量
DBUtil.closeAll(rs, ps, conn);
}
return flag;


}
@Override
public int save(Duty duty) {
//添加操作
//创建sql语句
String sql="insert into duty values(seq_duty.nextval,?,?,?,null)";
//创建params变量集合
Object[] params={duty.getEmpId(),duty.getDtDate(),duty.getSigninTime()};

return DBUtil.executeUpdate(sql, params);
6、在原来的jsp页面中,编写ajax请求,处理从servlet传来的数据

jsp页面添加方法

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<base href="<%=basePath%>" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<link href="css/style.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript">
//给按钮signin绑定单击事件,实现签到功能
$(function(){
$("#signin").click(function(){
//alert("ok");
//发送ajax请求,完成签到.并通过回调函数显示结果
$.ajax({
url:"duty?method=signin",
type:"post",
dataType:"text",
success:function(data){
//显示签到结果
if(data==0){
$("#result").html("签到失败");
}else if (data==1) {
$("#result").html("签到成功");
}else {
$("#result").html("已经签到,无需重复签到");
}
}
});
});

//给按钮signout绑定单击事件,实现签退
$("#signout").click(function(){
//alert("ok?");
$.ajax({
url:"duty?method=signout",
type:"post",
dataType:"text",
success:function(result){
$("#result").html(result);
}
});
});

});



</script>
</head>

<body>

<div class="place">
<span>位置:</span>
<ul class="placeul">
<li><a href="#">考勤管理</a></li>
<li><a href="#">签到签退</a></li>
</ul>
</div>

<div class="formbody">

<div class="formtitle"><span>基本信息</span></div>

<ul class="forminfo">
<li><label> </label><input name="signinTime" type="button" class="btn" id="signin" value="签到"/> 每天签到一次,不可重复签到</li>
<li><label> </label></li>
<li><label> </label></li>
<li><label> </label><input name="signoutTime" type="button" class="btn" id="signout" value="签退"/>可重复签退,以最后一次签退为准</li>
</ul>

</div>
<div id="result"></div>

</body>

</html>

签退功能

1、在servlet层编写签退相关方法(提前处理,ajax直接显示结果)

//考勤签退操作
public void signout(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

//获取session作用域中的emp对象
Employee emp= (Employee) request.getSession().getAttribute("emp");
String empId=emp.getEmpId();
//调用业务层完成签到操作
DutyService ds=new DutyServiceImpl();
int n=ds.signout(empId);
//0失败 1成功 2没有签到

//无需页面跳转,直接响应结果(通常与ajax连用)
response.setContentType("text/html; charset =utf-8");
PrintWriter out= response.getWriter();
if (n==1) {
out.println("签退成功");
} else if (n==0) {
out.println("签退失败");
} else {
out.println("尚未签退");
}
}
2、在service的实现类编写签退的逻辑方法

//考勤签退操作
public void signout(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

//获取session作用域中的emp对象
Employee emp= (Employee) request.getSession().getAttribute("emp");
String empId=emp.getEmpId();
//调用业务层完成签到操作
DutyService ds=new DutyServiceImpl();
int n=ds.signout(empId);
//0失败 1成功 2没有签到

//无需页面跳转,直接响应结果(通常与ajax连用)
response.setContentType("text/html; charset =utf-8");
PrintWriter out= response.getWriter();
if (n==1) {
out.println("签退成功");
} else if (n==0) {
out.println("签退失败");
} else {
out.println("尚未签退");
}
}
3、dao层调用的方法与签到一致,无需添加 4、在原来的jsp页面中,编写ajax请求,处理从servlet传来的数据(result为签到签退按钮下的一个div的id用来显示考勤结果)

jsp页面中的添加的方法/函数

//给按钮signout绑定单击事件,实现签退
  $("#signout").click(function(){
   //alert("ok?");
   $.ajax({
    url:"duty?method=signout",
    type:"post",
    dataType:"text",
    success:function(result){
     $("#result").html(result);
    }
   });
  });

注:采取mvc架构模式

总结:

签到实现 1、点击签到按钮,跳转到签到的servlet,调用相关的方法 2、dao层首先去数据库查看用户是否签到,如果签到则返回true,如果没签到则返回false,并执行保存签到信息的方法。返回签到的结果0失败,1成功,2已签到,并将数据返回到servlet 3、servlet将数据直接响应给前台页面,jsp页面通过Ajax获取信息,更根据相应的值显示相应的提示语。

签退实现 1、点击签退按钮,跳转到签退的servlet,调用相关的方法 2、dao层首先去数据库查看用户是否签退,如果签到则返回true,如果没签到则返回false,并执行保存签退信息的方法。返回签退的结果0失败,1成功,2已签到,并将数据返回到servlet 3、servlet将数据直接响应给前台页面,jsp页面通过Ajax获取信息,更根据相应的值显示相应的提示语。


项目地址(手动复制下面地址到浏览器即可): shimo.im/docs/IaCeHAzWu80UoG45