问卷星自动预设执行程序

前言:本程序限制较大,后期功能可以考虑慢慢加装

使用框架:koishi+napcat构建

需要知识:熟悉koishi的使用以及自编写koishi插件,JavaScript,napcat的相关操作

使用平台:QQ

缺点:根据需求的不同,本项目需要你提前了解有什么问题,我要选哪个做一个预设值。

速度:大概在2秒左右,根据问题数量依次提升

危险风度:可能会出现风控等原因导致的封号,目前正在慢慢寻找方法

正文:问卷星自动预设执行程序,也就是自动对问卷星中的内容执行固定的操作,来做到可以快速反应的作用。在设计开始前,需要思考两个问题,该如何检索到链接,该如何处理。这是两个不可避免必须要先解决的问题,得益于之前开发过QQ机器人,帖主我一下子就想到了使用QQ三方机器人来接受消息的方法,通过Koishi+napcat的框架构建,我们可以搭建一个三方QQ机器人来接受信息,并再额外对koishi内部写一个插件来自动填写。

使用koishi+napcat构建:

首先我们应该要明白如何使用koishi+napcat构建,参考文章:使用 1Panel 部署基于 Koishi + Napcat 的 QQ 机器人 - Bronya Zaychik (bronya-zaychik.cn),虽然docker确实非常的好用,但是你仍然可以通过本地下载一键集合包的方法来使用napcat,下载链接:https://github.com/NapNeko/NapCatQQ/releases/,一般来说你下载win64无头的即可使用。关于napcat和koishi的连接搭配就不必多说了,连接完成之后,你的机器人就可以接受到消息并对消息进行处理了,可以提前将机器人部署进入群内挂好以便及时反应。

第二,便是关于这个插件的编写,说是用koishi写插件,其实和写JavaScript差不多,用到了puppeteer库来模拟人工操作,首先先用npm下载puppeteer库,但是我推荐使用yarn来安装这个库,npm实在是太慢了,安装好了之后,便开始准备写吧,在写之前,我们要先去看一下问卷星的前端状态,我们需要了解如何快速找寻并填入,这里我自己开了一个问卷星来观察,进入浏览器点开f12观察前端代码即可。

点开f12,首先先观察填空框和提交组价。

这是第一个填空框。

这是第二个填空框,第三个设计的内容我隐藏掉了,但是还是能在前端查找到。

这是最后提交按钮的前端代码。

接下来就是喜闻乐见的找规律时刻,如果你熟悉html+css语言的话,你可以发现,这个填空真正起到输入作用的只有一个地方,那就是<input>标签,并且他们都含有一个独一无二的标识符,问题一的id就对应着q1,问题二的id就对应着q2,这是一个非常关键的标识符,提交按钮的标识符也是一样,通过观察,你可以发现他本质上是一个含有click事件的盒子,那么对于提交按钮,我们只需要模拟点击盒子就可以进行提交了。那么我们就可以尝试直接写了。

首先,就是接受消息并做一个正则比对,判断经过的信息流中是否含有问卷星标签的相关链接,如果有,那么便接受处理,如果没有,那么就过滤掉。

接下来开始使用puppeteer库来启动一个浏览器来帮助模拟用户操作,相关设置为如此,并且为了防止网页因网络波动原因导致的错误,额外添加一个含有重试机制的导航函数。

之后设置视口和User-Agent

在实际开发过程中,一定要学会在各处添加报错排查,那么前两个问题便可以这么写,并且可以在下面代码中的page.type当中添加你自己的预设值。

接下来进行最后的操作处理,在实际运行过程中,waitForNavigation很容易出现超时报错问题,官方的解决方法是,将提交的按键与切换导航给链接在一起执行。

之后结尾代码为:

但是在实际运行过程中发现,运行的速度甚至不如手速快一点的输入,通过不断查找资料,了解到是加载了过多的资源导致的,那么我们可以自动剔除掉不需要的资源渲染。

其中配置浏览器启动参数列表里面,headless一般设置为true,设置为false的原因是通过观察为了方便排查出错原因,在页面加载策略当中,可以尽可能的将一些不需要的资源给过滤掉。那么实际运行到这里,填空的自动输入就已经完成了。接下来就是一个有点坑的单选和多选了,首先依旧是观察他们的前端。

这是一个盒子,有一个input值被隐藏了,和填空差不多是吧,哈哈,如果你是这么想的话你就错了,是不是以为只需要简单的click一下那个input标签就是正确的,其实并不是,在实际运行过程中,我明白了这是一种操作管理<a>标签来实现的,也就是说,真正起到作用的内容实际上是标签<a>,这才是需要点击的,如果你去click那个input标签,那么在实际运行过程中,你只会看到好想确实被点击了,但是系统并没有认为被点击,那么我们就可以这样来编写。

通过索引第三个ui-radio事件当中的class=jqradio来帮助定位。只有这样,在实际运行中才是正确的,最后,附加上完整代码。

import { Context, Schema } from 'koishi'

const puppeteer = require('puppeteer')



export const name = 'getmessage'



export interface Config {}



export const Config: Schema<Config> = Schema.object({})



export function apply(ctx: Context) {

  // write your plugin here

  let browser;

  puppeteer.launch({ headless: true }).then(instance => browser = instance)

  // 监听消息事件

  ctx.on('message', async (session) => {

    // 使用正则匹配问卷星链接

    const wjxRegex = /(https?:\/\/(www\.)?wjx\.cn\/[^\s]+)/i

    const match = session.content.match(wjxRegex)

   

    if (!match) return // 如果没有匹配到链接则退出



    const url = match[0]

    // 配置浏览器启动参数

    const launchOptions = {

      headless: false,

      // 使用无GPU模式提高性能

      args: ['--disable-gpu', '--no-sandbox', '--disable-setuid-sandbox','--remote-debugging-port=0'],

      // 增加启动超时时间

      timeout: 30000

     };

    try {



      const browser = await puppeteer.launch(launchOptions);

      const page = await browser.newPage();

      page.setDefaultNavigationTimeout(30000); // 设置为30秒



      // 设置页面加载策略

      await page.setJavaScriptEnabled(true);

      await page.setRequestInterception(true);

      page.on('request', (req) => {

        if (['image','font'].includes(req.resourceType())) {

          req.abort();

        } else {

          req.continue();

        }

      });

      //await page.setJavaScriptEnabled(true);

      //await page.setImagesEnabled(false);

      //await page.setVideosEnabled(false);

      // await page.setRequestInterception(true);

      // 打开新页面

      // page.on('request', (req) => {

      //   if (['image', 'stylesheet', 'font', 'script'].includes(req.resourceType())) {

      //     req.abort(); // 阻止加载图片、样式表、字体和脚本

      //   } else {

      //     req.continue(); // 继续其他请求

      //   }

      // });

      // page.on('request', (req) => {

      //   if (['image', 'stylesheet','font'].includes(req.resourceType())) {

      //     req.abort(); // 阻止加载图片、样式表、字体和脚本

      //   } else {

      //     req.continue(); // 继续其他请求

      //   }

      // });

      // 使用重试机制的导航函数

      async function navigateWithRetry(maxRetries = 3) {

        let retryCount = 0;

        while (retryCount < maxRetries) {

          try {

            await page.goto(url, { waitUntil: 'load' });

            return true;

          } catch (err) {

            if (err instanceof puppeteer.errors.TimeoutError) {

              console.log(`导航超时,第${retryCount + 1}次重试...`);

              retryCount++;

            } else {

              throw err;

            }

          }

        }

        throw new Error(`无法在${maxRetries}次尝试内导航到页面`);

      }

      //设置视口和 User-Agent

      page.setViewport({ width: 1920, height: 1080 })

      page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36')



      // 访问问卷页面

      // await page.goto(url, { waitUntil: 'networkidle0' })

      await navigateWithRetry();

      console.log('重接进入网页功能处理完毕')

     

      // 示例:填写第一个文本输入框

      // await page.type('#q1', '')

      await page.waitForSelector('#q1', { visible: true });

      await page.type('#q1', '114514');

      console.log('问题1解决完毕')



      // await page.type('#q2', '')

      await page.waitForSelector('#q2', { visible: true });

      await page.type('#q2', '114514');

      console.log('问题2解决完毕')



      // 示例:选择第一个单选按钮

      // await page.waitForSelector('#q3_3',{visible:true});

      // await page.click('#q3_3')

      // const isElementVisible = await page.evaluate((selector) => {

      //   const element = document.querySelector(selector);

      //   return element ? element.offsetWidth > 0 && element.offsetHeight > 0 : false;

      // }, '#q3_3');

     

      // if (isElementVisible) {

      //   await page.click('#q3_3');

      // } else {

      //   console.log('Element #q3_3 is not visible');

      // }

      // await page.evaluate(() => {

      //   const radio = document.getElementById('q4_3');

      //   if (radio) {

      //     radio.click();

      //   }

      // })

      // await page.evaluate(() => {

      //   const radio = document.getElementById('q4_3');

      //   if (radio) {

      //     // 创建一个鼠标事件

      //     const event = new MouseEvent('click', {

      //       view: window,

      //       bubbles: true,

      //       cancelable: true

      //     });

      //     // 触发点击事件

      //     radio.dispatchEvent(event);

      //   }

      // });

      await page.waitForSelector('.ui-radio');

      await page.click('.ui-radio:nth-of-type(3) .jqradio');

     

      console.log('问题三解决完毕')

      //await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒

     



      // 提交问卷(根据实际页面结构修改)

      // await page.click('#ctlNext')

      //await page.click('#ctlNext');



      // 等待提交完成

      // 同时等待页面导航和点击操作

      // await page.click('#ctlNext')

      // await page.waitForNavigation()

      const [response] = await Promise.all([ // 点击操作

        page.waitForNavigation(),

        page.click('#ctlNext')// 等待网络空闲

      ]);

      console.log('导航栏以及结束处理完毕')



      //await page.waitForNavigation()

     

      //await asyncio.wait([page.waitForNavigation(),page.click('#ctlNext')])

      // 关闭页面

      await page.close()

      console.log('完毕页面处理完毕')



      // 发送反馈

      //await session.send('问卷已自动填写并提交!')

      console.log('success')

    } catch (err) {

      console.log('mission fail')

      console.error('问卷处理失败:', err)

      //await session.send('问卷自动处理失败,请手动处理。')

    }

  })

  // 关闭浏览器实例

  ctx.on('dispose', async () => {

    if (browser) await browser.close()

  })

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值