【油猴脚本】关于用油猴脚本爬取考试题库这件事 – CheungQ 您所在的位置:网站首页 怎么搜试卷 【油猴脚本】关于用油猴脚本爬取考试题库这件事 – CheungQ

【油猴脚本】关于用油猴脚本爬取考试题库这件事 – CheungQ

2024-07-11 00:42| 来源: 网络整理| 查看: 265

公司每隔一段时间都有一些规范性的内容的考试,略头疼,有一点还行的是事先都提供了题库,于是本着自己动手丰衣足食的精神,用React写了个题库搜题的小Web应用

搜题小应用倒是好写,这个省去不说。但是题库的录入就有点费事了,从一开始写搜题Web应用到最终这个脚本诞生,中间经历了几个阶段

1.手动录入题目,费事费力,是个重体力活

2.手机给题目截图,用图片的方式展示搜题结果,工作量依旧很大,一开始设想的是图片保存后接一个ORC服务,来实现关键文字信息提取,但是接入ORC服务这依旧是一个费力的过程,还不说ORC服务是否需要付费,和手机上截图的背景对准确性产生的影响

3.在经历了前面两次考试之后,刷题背题的过程中才发现是页面文字是可以复制出来的,于是这一代的就开始了一题题的复制题目,存在手机备忘录,然后再在电脑上取得一整个所有题目的大又长的字符串,再做一些字符串解析的工作,解析之后就可以使用了。看起来不错,不过一题题的复制,依旧还是一个不轻松的工作,而且后面的字符串解析也实现起来有点点麻烦,各个字段,题目之间的分割特征不是很明显

4.抓包软件的应用,在实现3的时候其实想到了可以使用抓包软件来实现,不过最终没有走上这一步,而且如果要实现这一步的话,最终每次接口请求的数据也都需要人工收集

5.经过观察确定,这个题库本质其实是个web页面,那么既然是web页面了,必然还是可以在电脑上用浏览器打开的吧,假如服务端没有做一些特别的限制的话,经过几次尝试果然,确实可以打开,那么我们的油猴脚本就有大展身手的机会了

基本思路:

实现一个自己的XMLHttpRequest方法,替换掉window上原本的XMLHttpRequest方法,并对特定的事件进行记录,那么我们需要的数据也就水到渠成的可以截取下来。中间还用到了CustomEvent 。具体的就可以看下面代码了,从一开始的初始版本可以截取信息,到最终用起来顺手,还是经过了几个版本的迭代的

// ==UserScript== // @name 题库小柯基(科技) // @namespace http://tampermonkey.net/ // @version 1.3 // @description try to take over the world! // @author You // @match https://xxxxxx.com?* // @grant none // ==/UserScript== //需要存下来的数据 window.list = []; //题库问题唯一编码列表.用来去重以防重复存储 window.subjectCodeList = [] //中断执行标记 window.ifInterrupt = true //总页数 let totalCount = () => document.getElementsByClassName("examNumbers")[0].children[3].innerText //当前页码 let currentNum = () => document.getElementsByClassName("examNumbers")[0].children[1].innerText let isEnd = () => (currentNum() - 0) >= (totalCount() - 0) let clearList = () => { window.list = [] window.subjectCodeList = [] } let goNext = () => { window.ifInterrupt = false !isEnd() && document.getElementsByClassName('nextBtnClass')[0].click() } /** * 在页面上创建一个按钮,用来触发实现对应需要的功能 * @param func * @param btnText * @param top */ let createButton = (func, btnText, top) => { let btn = document.createElement("button") btn.style.position = "fixed" btn.style.right = 0 btn.style.top = top btn.style.padding = "10px" btn.style.zIndex = 99999 btn.innerText = btnText btn.addEventListener("click", func) document.body.append(btn) } createButton(clearList, "清除缓存", "120px") createButton(goNext, "开始抓取", "80px") createButton(() => window.ifInterrupt = true, "暂停", "160px") ;(function () { if (typeof window.CustomEvent === "function") return false; function CustomEvent(event, params) { params = params || {bubbles: false, cancelable: false, detail: undefined}; let evt = document.createEvent('CustomEvent'); evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); return evt; } CustomEvent.prototype = window.Event.prototype; window.CustomEvent = CustomEvent; })(); ;(function () { function ajaxEventTrigger(event) { let ajaxEvent = new CustomEvent(event, {detail: this}); window.dispatchEvent(ajaxEvent); } let oldXHR = window.XMLHttpRequest; function newXHR() { let realXHR = new oldXHR(); realXHR.addEventListener('abort', function () { ajaxEventTrigger.call(this, 'ajaxAbort'); }, false); realXHR.addEventListener('error', function () { ajaxEventTrigger.call(this, 'ajaxError'); }, false); realXHR.addEventListener('load', function () { ajaxEventTrigger.call(this, 'ajaxLoad'); }, false); realXHR.addEventListener('loadstart', function () { ajaxEventTrigger.call(this, 'ajaxLoadStart'); }, false); realXHR.addEventListener('progress', function () { ajaxEventTrigger.call(this, 'ajaxProgress'); }, false); realXHR.addEventListener('timeout', function () { ajaxEventTrigger.call(this, 'ajaxTimeout'); }, false); realXHR.addEventListener('loadend', function () { ajaxEventTrigger.call(this, 'ajaxLoadEnd'); }, false); realXHR.addEventListener('readystatechange', function () { ajaxEventTrigger.call(this, 'ajaxReadyStateChange'); }, false); let send = realXHR.send; realXHR.send = function (...arg) { send.apply(realXHR, arg); realXHR.body = arg[0]; ajaxEventTrigger.call(realXHR, 'ajaxSend'); } let open = realXHR.open; realXHR.open = function (...arg) { open.apply(realXHR, arg) realXHR.method = arg[0]; realXHR.orignUrl = arg[1]; realXHR.async = arg[2]; ajaxEventTrigger.call(realXHR, 'ajaxOpen'); } let setRequestHeader = realXHR.setRequestHeader; realXHR.requestHeader = {}; realXHR.setRequestHeader = function (name, value) { realXHR.requestHeader[name] = value; setRequestHeader.call(realXHR, name, value) } return realXHR; } window.XMLHttpRequest = newXHR; })(); window.addEventListener("ajaxReadyStateChange", function (e) { let xhr = e.detail; if (xhr.readyState == 4 && xhr.status == 200) { // xhr.getAllResponseHeaders() 响应头信息 // xhr.requestHeader 请求头信息 // xhr.responseURL 请求的地址 // xhr.responseText 响应内容 // xhr.orignUrl 请求的原始参数地址 // xhr.body post参数,(get参数在url上面) let url = xhr.orignUrl //只需关注我们需要的url,其他忽略 if ("/url-witch-need-to-be-listen.json" == url) { //最终监听到的接口返回的信息 let json = JSON.parse(xhr.responseText); let {subjectCode} = json if (window.subjectCodeList.includes(subjectCode)){ return } window.list.push(createQuestionItem(json)) window.subjectCodeList.push(subjectCode) console.log(window.list) //判断是否到了最后一页,以及是否需要中断本次执行 if (!window.ifInterrupt && !isEnd()) { //随机一个5秒内的时间,点击下一页按钮的操作。触发下一次请求 setTimeout(() => { document.getElementsByClassName('nextBtnClass').length && document.getElementsByClassName('nextBtnClass')[0].click() }, Math.random() * 5000) } if (isEnd()) { console.warn("结束啦") //最终创建一个新的window展示抓取下来的内容,方便后续录入题库操作 let newWindow = window.open('', '获取结果', 'height=300,width=400,top=0,left=0,toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no') newWindow.document.body.innerText = JSON.stringify(window.list) } } } }); let createQuestionItem = ( questionInfo ) => { let type, question, answerArr, answer, desc; return { type, question, answerArr, answer, desc, questionInfo } }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有