学习 / 技术分享

利用puppeteer自动填报疫情信息

越权(0x3E5) · 5月18日 · 2020年 · · · · ·

前段时间已经成功逆向出了学校app的登录接口然后实现了疫情信息自动填报功能并且实现了QQ机器人对填写结果的实时推送

0x01 工具介绍

  • Node.js – 可以让JavaScript运行在服务端的运行环境
  • puppeteer – Node.js下的前端自动化测试工具
  • Vscode – 一款超级好用的编辑器
  • mirai – 开源QQ聊天机器人

0x02 基础环境构建

  对于Node.js等环境在此不再过多讲述,网上安装教程一大把。直接从安装依赖开始。
首先在项目目录中安装依赖(由于axios设置的请求头信息Content-Type不支持text/plain所以需要安装request)。

npm i axios request puppeteer -S

  依赖安装完毕引入根据上次抓到的app登录接口封装的登录方法login()

0x03 编写发送消息机器人

  Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持的高效率机器人库。并且其提供了丰富的插件。本项目使用的是 marai-consolenode-mirai开发插件。
  由于本项目只需要将结果发送到指定qq所以通过阅读官方提供的node-mirai的插件源码自行封装一个发送消息的功能。代码如下:

// qq.js
const axios = require('axios')

// 获取版本号
function getVersion(){
  return new Promise((resolve,reject)=>{
    axios.get('http://域名或ip/about')
    .then(res=>{
      resolve(res.data);
    })
    .catch(err=>{
      reject('')
    })
  })
};
// 获取Session
function getAuth(authKey){
  return new Promise((resolve,reject)=>{
    axios.post('http://域名或ip/auth',{
      "authKey": authKey
    })
      .then(res=>{
        resolve(res.data.session)
      })
      .catch(err=>{
        reject('')
      })
  })
}
// 验证绑定
function getVerify(qq,session){
  return new Promise((resolve,reject)=>{
    axio本文来自:1024s.cns.post('http://域名或ip/verify',{
      "sessionKey": session,
      "qq": qq
    })
      .then(res=>{
        resolve(res.data.msg)
      })
      .catch(err=>{
        reject('')
      })
  })
}
// 释放绑定
function getRelease(qq,session){
  return new Promise((resolve,reject)=>{
    axios.post('http://域名或ip/release',{
      "sessionKey": session,
      "qq": qq
    })
      .then(res=>{
        resolve(res.data.msg)
      })
      .catch(err=>{
        reject('')
      })
  })
}
// 发送消息
function qqMsg(qq,msg,session){
  return new P1024s.cnromise((resolve,reject)=>{
    axios.post('http://域名或ip/sendFriendMessage',{
      "sessionKey": session,
      "target": qq,
      "messageChain": [{ "type": "Plain", "text":msg }]
    })
      .then(res=>{
        resolve(res.data.msg)
      })
      .catch(err=>{
        reject('')
      })
  })
}

async function sendMsg(qq,msg){
  const session = await getAuth('设置的Auth');
  if(session){
    const stat本文来自:1024s.cne = await getVerify('服务端登录的qq',session)
    if(state==='success'){
      const sendResult = await qqMsg(qq,msg,session);
      if(sendResult==='success'){
        consol1024s.cne.log('[Success] 通知消息发送成功');
      }else{
        console.log('[Error] 通知消息发送失败');
      }
      const releaseResult = await getRelease('服务端登录的qq',session);
      if(releaseResult==='success'){
        console.log('[Success] 机器人释放成功');
      }else{
        console.log('[Error] 机器人释放失败');
      }
    }else{
      console.log('[Error] 绑定机器人失败');
    }
  }else{
    console.log('[Error] 获取机器人Session失败');
  }
}
module.exports=sendMsg

  在 mirai-console 的插件目录(plugins)放入 mirai-api-http 插件插件的具体配置可以参考github的说明文档。然后在windows服务上启动 miri-console 服务

  启动成功后,在命令行中输入 login qq号码 密码 来登录机器人qq,到此qq消息机器人搭建完成

0x04 使用puppeteer实现疫情信息自动填写

  puppeteer可以模拟用户操作访问网页填写表单并且支持无头浏览器模式。在通过 npm 安装puppeteer插件时会自动下载一个 chromium 浏览器如果是在linux下运行则会提示缺少依赖可以通过安装相关依赖解决

#依赖库
yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y
#字体
yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1本文来自:1024s.cn xorg-x11-fo1024s.cnnts-misc -y

  引入自己封装好的相关方法开始封装疫情自动填写代码。

// reportSubmit.js
'use strict';
const login = require('./***');
const sendMsg = require('./qq')
const puppeteer = require('puppeteer');

// 自动提交疫情防控表单
async function sendReport({username,password,本文来自:1024s.cnnativePlace,homeAddr,nowAddr,qq}) {

  // 用户登录
  let {userId, userName, uniquePowered by 0x3E5Code, encToken} = await login(username,password);

  if(userId && userName && uniqueCode && encToken ){
    console.log('[Log] 用户登录信息获取成功');
    console.log(`[Log] 当前用户学号:${userId}`);
    console.log(`[Log] 当前用户姓名:${userName}`);

    // 模拟用户操作开启浏览器
    // headless:false 即可开启浏览器界面 true则表示无头浏览器
     const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox'],headless: true});

    // 新建页面
    const page = await browser.newPage();

    // 设置请求头
    await page.setExtraHTTPHeaders({
      'access-token':encToken,
      'unique-code':uniqueCode
    })

    // 根据响应头跳转页面
    await page.on('response',response=>{
      let resHeader = response.headers();
      if(resHeader.location){
        page.setExtraHTTPHeaders({}) // 清空请求头
      }
    })

    // 跳转链接
    await page.goto(`疫情填报接口地址`);

    // 等待页面渲染完成
    const li = await page.waitForSelector('li').catch(err=>{});
    if(!li){
      console.log('[Log] 没有获取到疫情防控表');
      await browser.close();
      return '';
    }
    // 判断是否已经填写过
    const isWrite = await page.$x('//*[@class="no_write"]')
    if(isWrite.length){
      // 点击跳转
      (await page.$x('//*[@class="time"]'))[0].click({delay:200});
      await console.log('[Log] 成功进入疫情防控表');

      // 等待表单渲染完成
      await page.waitForSelector('.form_box');
      console.log('[Log] 开始填写疫情防控信息');

      // 开始填写表单信息
      (await page.$x('//*[@class="form_box"]/div/div[5]/div/input'))[0].type(nativePlace);
      console.log(`[Log] 籍贯信息: ${nativePlace}`);
      await page.waitFor(1000);
      (await page.$x('//*[@class="form_box"]/div/div[6]/div/input'))[0].type(homeAddr);
      console.log(`[Log] 家庭地址: ${homeAddr}`);
      await page.wa1024s.cnitFor(1000);
      (await page.$x('//*[@class="form_box"]/div/div[7]/div/input'))[0].type(nowAddr);
      console.log(`[Log] 现居地哦: ${nowAddr}`);
      await page.waitFor(1000);
      for(i = 8; i < 17; i++){
        (await page.$x(`//*[@class="form_box"]/div/div[${i}]/div/label/span/inp1024s.cnut`))[0].click();
        await page.waitFor(500);
      };

      // 提交表单
      (await page.$x('//div[@class="WriteTask_Detail"]/div[4]/a'))[0].click({delay:1500});
      console.log('[Success] 提交疫情防控信息成功');
      await sendMsg(qq,`时间:${new Date()}\n学号:${username}\n姓名:${userName}\n状态:疫情防控报告提交成功`)
      // 关闭浏览器
      await page.waitFor(2000);
      await browser.close();
    }else{
      console.log('[Log] 您已经提交过或撤销报告了');
      await sendMsg(qq,`时间:${new Date()}\n学号:${username}\n姓名:${userName}\n状态:您已经提交过或撤销报告了`)
      await page.waitFor(500);
      await browser.close();
    }
  }else{
    console.log('[Error] 登录失败请检查登录信息');
    console.log(`[Error] 当前用户学号: ${username}`);
    await sendMsg(qq,`时间:${new Date()}\n学号:${username}\n状态:登录失败请联系管理员检查登录信息`)
  }
}

module.exports = sendReport;

0x05 调用疫情自动填写方法设置定时任务


  将代码上传到服务器并设置任务定时执行,大功告成!下面附上服务器打印日志以及机器人每天自动推送的填写结果。