ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

问卷星自动填写脚本实战:DOM操作与事件模拟全解析

问卷星自动填写脚本实战:DOM操作与事件模拟全解析 简介本资源是支持问卷星自动随机填写的JS插件由作者Ahaochan分享适合需要批量填写问卷、测试问卷逻辑或进行调研数据模拟的普通用户与开发者。插件目前可覆盖单选题、多选题、比重题等常见题型通过浏览器油猴扩展或其他脚本管理器即可加载使用无需复杂安装步骤有效提升问卷填写效率。压缩包共5个文件整体大小约9KB核心为js脚本文件另附3个url快捷方式与1个txt说明文档可用于了解脚本来源及使用方法。该资源已有9900余人学习下载具备较好的实践参考价值。对于经常接触问卷星表单、希望自动生成随机答案或避免手动重复操作的用户来说这套轻量脚本能够直接提供可用方案帮助快速完成填写任务同时也可作为学习JS脚本编写与浏览器扩展应用的入门实例。 做过表单自动化的人都知道问卷星这类在线问卷系统表面上看起来只是普通网页表单真写起自动填写脚本时却总能在意想不到的地方卡住。我最早写问卷星自动填写脚本是被产品同事逼出来的上线前要拿随机分布的测试数据去验证后台统计报表二十份问卷每份三十多道题手动填到第五份就快崩溃了尤其是矩阵量表题点完一页手指都酸。于是花了一个晚上写了套 js 自动随机填写脚本跑起来之后刷新、填写、提交整个过程完全不用动手二十份测试数据十分钟搞定。这篇文章就把这套脚本的思路、代码和踩过的坑完整梳理一遍适合自己创建问卷时的自动化测试、前后端联调演示以及想学浏览器自动化脚本的朋友参考。1. 问卷星自动填写脚本到底能解决什么问题1.1 我为什么会写这个脚本很多人看到自动随机填写第一反应是这不就是用来刷问卷的我不否认网上确实有人拿它干这个但就我个人经验来说这个脚本最初是为了解决一个非常正经的痛点——给后台统计系统制造测试数据。当时我们有个满意度调研的报表模块要上线需要一批不同选项分布的样本数据去验证统计逻辑。产品经理要求别都选满意得有一定随机性。这份问卷光单选多选就二十多道还有两个矩阵量表题手动填完一份要四五分钟要填二十份谁填谁崩溃。所以我的需求很明确自动识别问卷里的所有题目按题型生成合理的随机答案自动点击提交能连刷多份最好这个脚本就是围绕这三个需求长出来的。后来我发现它在另外两个场合也很好用一是快速演示问卷流程不用自己一个个点选项二是前端同事调试页面联动逻辑时需要反复提交不同选项组合手动改太慢。1.2 脚本的能力边界把能做和不能做的事情理顺能少走很多弯路。我自己列了一张表能力方向能做到做不到单选题随机选择一个选项按概率权重控制选择分布多选题随机选 1 到 N 个选项保证选项之间逻辑互斥填空题根据题目描述生成随机文本理解题目语义后精准作答矩阵量表题每一行随机选一个刻度自动保持行间逻辑一致提交环节自动点击提交按钮绕过滑块验证码、短信验证频率控制脚本内做随机延迟绕过问卷星后台的频控策略这张表建议想用脚本的人先看一遍。脚本解决的是机械重复点击的问题解决不了答题逻辑和真实性的问题。你可以在自己创建的问卷上无限次测试提交但如果你想拿它去填别人发来的正式调研问卷填出来的数据基本没有参考价值这不是脚本的问题而是随机答案本来就不能代表真实用户选择。1.3 免费最新版到底指什么输入标题里带免费最新版这个词我多说一句。脚本本身就是一段 JavaScript 代码本质上没有免费版付费版的区分代码在网上传的都是免费开源的一份。它谈版本不如谈适配。问卷星页面结构更新过很多次从早年的 table 布局切换到了 div 布局class 类名也换过不同时段克隆出来的问卷页结构甚至不太一样。所谓最新版的脚本通常是作者更新了选择器让它能匹配当前页面的 DOM 结构。这个现实意味着如果你拿到的脚本跑不起来优先考虑是不是页面结构变了而不是脚本坏了。2. 动手前先搞清楚问卷星页面的运行机制2.1 一道题在 DOM 里长什么样写脚本前必须先知道一件事问卷星页面不是一张静态 HTML而是一个动态渲染的网页应用。打开开发者工具看结构你会发现每道题基本都被包在一个带.fieldset或.q-item类名的容器里再根据题型细分单选题容器内是多个label每个 label 里包着一个input[typeradio]多选题同样的结构但 input 类型是checkbox填空题/简答题一个textarea短标题类填空可能是input[typetext]量表/矩阵题一个表格或 grid 布局每一行左侧是标题行右侧是一组radio下拉选择题一个select元素先打开 F12 确认页面的实际类名再动手写选择器。我的经验是不要只看问卷星的公共类名还要看它渲染出来的结构有时候同一个问卷在 PC 端和手机端类名并不完全一致。2.2 为什么不能直接改 value 就完事这是很多初学者最容易踩的认知误区我用 JS 把input的value改掉再把checked设成 true提交不就行了吗实测下来大概率不行。原因在于现代前端框架和事件绑定机制。问卷星的单选、多选选项被点击后不仅要设置选中状态还要触发一组监听事件让问卷系统更新内部状态——比如判断必答题有没有答完、下一题是否解锁、提交按钮是否可用。只改checked属性选项看起来是选中了但系统内部记录答案的对象没有更新提交时照样被判为本题未回答。所以正确做法是模拟真实用户的操作调用click()再手动派发change和input事件让框架的监听器感知到变化。2.3 iframe 和分页加载的影响问卷星有两种常见的页面形态一种是直接打开问卷链接脚本直接操作当前页面 DOM另一种是问卷嵌入到第三方系统页面里整体包在一个iframe中。如果脚本写的是document.querySelector在嵌入场景下永远找不到题目因为问题 DOM 在iframe.contentDocument里。分页问卷是另一个变量。问卷可以设置分页点下一页之后才加载新题。这意味着脚本不能只在页面加载完成后跑一次必须在点击下一页之后等待新题目渲染再继续处理后续题目。没处理这两件事的脚本很容易出现只填了第一页或者全部选择器失效的诡异问题。3. 脚本核心实现从 DOM 定位到随机填写的完整链路3.1 先写一个通用的题目扫描器脚本的第一步是把页面上所有题目容器找出来。考虑到不同版本问卷星类名有差异我习惯写一个容错性更强的扫描函数同时匹配几种常见类名function getQuestionItems(rootDoc) { const selectors [.fieldset, .q-item, div.q-form-item]; for (let i 0; i selectors.length; i) { const items rootDoc.querySelectorAll(selectors[i]); if (items.length 0) { return Array.from(items).filter(item { const style item.getAttribute(style) || ; return style.indexOf(display: none) -1; }); } } return []; }这里做两层筛选先用类名定位再过滤掉带display:none的隐藏块。隐藏题目通常出现在根据上一题答案显示下一题这种逻辑题里不过滤的话脚本会把隐藏题也填了反而可能触发逻辑冲突。3.2 不同题型的不同随机策略拿到题目容器后要按题型分别处理。我的核心函数长这样function randomFillItem(item) { const rootDoc item.ownerDocument; const radios item.querySelectorAll(input[typeradio]); const checkboxes item.querySelectorAll(input[typecheckbox]); const textareas item.querySelectorAll(textarea); const textInputs item.querySelectorAll(input[typetext], input:not([type])); const selects item.querySelectorAll(select); if (selects.length 0) { const select selects[0]; const options Array.from(select.options).filter(opt opt.value); const randomOpt options[Math.floor(Math.random() * options.length)]; select.value randomOpt.value; select.dispatchEvent(new Event(change, { bubbles: true })); } else if (radios.length 0) { const target radios[Math.floor(Math.random() * radios.length)]; target.click(); target.dispatchEvent(new Event(change, { bubbles: true })); } else if (checkboxes.length 0) { const count 1 Math.floor(Math.random() * checkboxes.length); const shuffled shuffleArray(Array.from(checkboxes)).slice(0, count); shuffled.forEach(cb { cb.click(); cb.dispatchEvent(new Event(change, { bubbles: true })); }); } else if (textareas.length 0) { const fakeText generateTextByField(item); textareas[0].value fakeText; textareas[0].dispatchEvent(new Event(input, { bubbles: true })); } else if (textInputs.length 0) { const fakeText generateTextByField(item); textInputs[0].value fakeText; textInputs[0].dispatchEvent(new Event(input, { bubbles: true })); } }注意几个细节。多选的逻辑是先随机决定选几个再随机选哪几个而不是固定全选。填空题不能随便塞无意义字符我后面会讲怎么根据题目标题生成相对合理的假文本。下拉题不能光设置 value必须派发change否则页面可能不认为你选了。3.3 触发事件与提交按钮前面说过原生事件的重要性。代码里的dispatchEvent(new Event(change, { bubbles: true }))就是干这个的。有些版本的问卷星用 Vue 实现还需要额外触发input事件才更新 v-model 绑定。所以我得把事件触发写得更彻底一点function triggerInput(el) { const events [change, input]; events.forEach(type { el.dispatchEvent(new Event(type, { bubbles: true, cancelable: true })); }); }填完之后就是提交。提交按钮在问卷星的类名也不固定我习惯按按钮文字匹配找到后直接 clickfunction findSubmitButton(rootDoc) { const buttons Array.from(rootDoc.querySelectorAll(button, div, a)); return buttons.find(el { const text (el.innerText || ).trim(); return text 提交 || text 下一页 || text 下一题; }); }如果你要填的是分页问卷点下一页的时候还要再对新出现的题目容器跑一次randomFillItem。3.4 完整脚本示例把上面几个函数组合起来就得到了一个能跑的最小完整脚本(function () { function shuffleArray(arr) { const copy arr.slice(); for (let i copy.length - 1; i 0; i--) { const j Math.floor(Math.random() * (i 1)); [copy[i], copy[j]] [copy[j], copy[i]]; } return copy; } function getQuestionItems(rootDoc) { const selectors [.fieldset, .q-item, div.q-form-item]; for (let i 0; i selectors.length; i) { const items rootDoc.querySelectorAll(selectors[i]); if (items.length 0) { return Array.from(items).filter(item { return !(item.getAttribute(style) || ).includes(display: none); }); } } return []; } function generateTextByField(item) { const title (item.innerText || ).substring(0, 8); const bank 我们注意到该问题对整体体验有直接影响建议后续持续关注并优化相关流程。.split(); let text ; for (let i 0; i 12; i) { text bank[Math.floor(Math.random() * bank.length)]; } return 测试反馈${title}${text}; } function randomFillItem(item) { const rootDoc item.ownerDocument; const selects item.querySelectorAll(select); const radios item.querySelectorAll(input[typeradio]); const checkboxes item.querySelectorAll(input[typecheckbox]); const textareas item.querySelectorAll(textarea); const textInputs item.querySelectorAll(input[typetext], input:not([type])); if (selects.length 0) { const select selects[0]; const options Array.from(select.options).filter(opt opt.value); select.value options[Math.floor(Math.random() * options.length)].value; select.dispatchEvent(new Event(change, { bubbles: true })); return; } if (radios.length 0) { const target radios[Math.floor(Math.random() * radios.length)]; target.click(); target.dispatchEvent(new Event(change, { bubbles: true })); return; } if (checkboxes.length 0) { const count 1 Math.floor(Math.random() * checkboxes.length); shuffleArray(Array.from(checkboxes)).slice(0, count).forEach(cb { cb.click(); cb.dispatchEvent(new Event(change, { bubbles: true })); }); return; } if (textareas.length 0) { textareas[0].value generateTextByField(item); textareas[0].dispatchEvent(new Event(input, { bubbles: true })); return; } if (textInputs.length 0) { textInputs[0].value generateTextByField(item); textInputs[0].dispatchEvent(new Event(input, { bubbles: true })); } } const doc window.document; const items getQuestionItems(doc); items.forEach(randomFillItem); const submitBtn Array.from(doc.querySelectorAll(button, div, a)).find(el { const text (el.innerText || ).trim(); return text 提交 || text 下一页; }); if (submitBtn) submitBtn.click(); console.log([auto-fill] 共填写 ${items.length} 道题目); })();这段代码在控制台直接粘贴就能跑适合当前多数问卷星页面的基础结构。如果跑完发现某道题漏了多半是类名变了打开 F12 把实际类名换进 selectors 里即可。4. 实战中踩过的坑iframe、事件触发与提交校验4.1 问卷套在 iframe 里选择器全部失效第一次写完之后我拿一个嵌在内部系统里的问卷测试控制台直接报错题目数量是 0。花了几分钟才反应过来问卷页面是嵌在 iframe 里的外层document根本拿不到问卷内容。解决方式有两种。如果 iframe 和父页面同源可以直接穿透访问const frames document.querySelectorAll(iframe); let targetDoc null; for (let i 0; i frames.length; i) { try { const inner frames[i].contentDocument; if (inner inner.querySelector(.fieldset)) { targetDoc inner; break; } } catch (e) { // 跨域无法访问继续找 } }如果 iframe 跨域父页面脚本无法访问其内容那就得直接在 iframe 自身的调试环境里跑脚本或者干脆单独打开问卷链接用独立页面模式跑。4.2 click 事件不生效数据却显示为空这个坑是我花时间最多的一个。单选按钮用target.click()点了页面上选项也高亮了但提交后后台收到的数据里这道题是空的。后来我排查发现问题出在click()触发的是鼠标点击绑定但问卷星的某些组件同时监听change事件比如changehandleAnswerChange这类绑定。点击已选中页面也显示了效果可框架的内部状态没有被监听函数更新。我的调整方案就是那行dispatchEvent(new Event(change, { bubbles: true }))。加上之后后台确实能收到数据了。这个经验也适用于其他表单自动化脚本不要假设界面看起来选中了就等于框架内部状态更新了。4.3 提交时提示还有题目未完成的定位过程有一版脚本跑完控制台显示填了 25 道题但点击提交后页面还是弹提示请完成所有必答题。我最初的怀疑是某些选择器没匹配上于是把脚本改成逐步输出每道题的状态结果发现数量接近全部题目。继续排查才发现问题出在两处一是矩阵量表题处理不完整——矩本文还有配套的精品资源点击获取
返回列表