q11e-wjw

为问卷网量身打造的全新自动化程序

  1. // ==UserScript==
  2. // @name q11e-wjw
  3. // @namespace https://bbs.tampermonkey.net.cn/
  4. // @version 1.0.1
  5. // @description 为问卷网量身打造的全新自动化程序
  6. // @author zemelee
  7. // @license CC-BY-NC-4.0
  8. // @homepageURL https://github.com/zemelee
  9. // @homepageURL http://sugarblack.top
  10. // @match https://www.wenjuan.com/*
  11. // ==/UserScript==
  12. function clearAll() {
  13. localStorage.clear()
  14. sessionStorage.clear()
  15. const cookies = document.cookie.split(";");
  16. for (let cookie of cookies) {
  17. const name = cookie.split("=")[0].trim();
  18. document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
  19. }
  20. }
  21.  
  22. async function sleep(delay) {
  23. return new Promise((resolve) => { setTimeout(resolve, delay * 1000) });
  24. }
  25.  
  26. function showMessage(message, type = "error") {
  27. const toast = document.createElement("div");
  28. toast.textContent = message;
  29. toast.style.position = "fixed";
  30. toast.style.top = "20px";
  31. toast.style.right = "20px";
  32. toast.style.padding = "10px 20px";
  33. toast.style.backgroundColor = {
  34. info: "#3498db",
  35. success: "#2ecc71",
  36. warning: "#f39c12",
  37. error: "#e74c3c"
  38. }[type] || "#3498db";
  39. toast.style.color = "white";
  40. toast.style.borderRadius = "5px";
  41. toast.style.zIndex = "9999";
  42. toast.style.transition = "opacity 0.5s";
  43. document.body.appendChild(toast);
  44. setTimeout(() => {
  45. toast.style.opacity = "0";
  46. setTimeout(() => document.body.removeChild(toast), 500);
  47. }, 3000);
  48. }
  49.  
  50.  
  51. function safeExecute(fn) {
  52. return async function (current, ...rest) {
  53. try {
  54. await fn.call(this, current, ...rest);
  55. } catch (error) {
  56. showMessage(`第${current}题有误!`);
  57. }
  58. };
  59. }
  60.  
  61. function checkAgain() {
  62. return Array.from(document.querySelectorAll('div')).find(el => {
  63. return el.textContent.trim() === '再次参与';
  64. });
  65. }
  66.  
  67. async function clickAgain() {
  68. const againDiv = checkAgain()
  69. if (againDiv) {
  70. againDiv.click();
  71. await sleep(2);
  72. }
  73. }
  74.  
  75. async function clickRestart() {
  76. const restart = Array.from(document.querySelectorAll('span')).find(el => {
  77. return el.textContent.trim() === '重新开始';
  78. });
  79. if (restart) {
  80. restart.click();
  81. await sleep(2);
  82. }
  83. }
  84.  
  85. async function clickContinue() {
  86. const elements = document.querySelectorAll('p');
  87. for (let el of elements) {
  88. if (el.textContent.trim() === '继续答题') {
  89. el.click()
  90. }
  91. }
  92. await sleep(1);
  93. }
  94.  
  95. function singleRatio(range, ratio) {
  96. let weight = [];
  97. let sum = 0;
  98. for (let i = range[0]; i <= range[1]; i++) {
  99. sum += ratio[i - range[0]];
  100. weight.push(sum);
  101. }
  102. const rand = Math.random() * sum;
  103. for (let i = 0; i < weight.length; i++) {
  104. if (rand < weight[i]) {
  105. return i + range[0];
  106. }
  107. }
  108. }
  109.  
  110. function randint(a, b) {
  111. return Math.floor(Math.random() * (b - a + 1)) + a;
  112. }
  113.  
  114. const optCountable = {
  115. single: "div.q-single>div.option-group>div>div>div",
  116. multiple: "div.q-multiple>div.ws-checkbox-group>div>div>div",
  117. bank: "textarea",
  118. scoreDefault: "div.q-score-default>div>div.icon-topic>div>div>div",
  119. star: "div.q-score-default>div>div.icon-topic>div>div>div",
  120. matrixScale: {
  121. qs: "div.q-matrix-scale>div>div>div.option-row-content",
  122. opts: "div.icon-topic-content>div.row-style"
  123. },
  124. evaluation: "div.q-evaluation > div > div.evaluation-top"
  125. };
  126.  
  127. async function _single(current, ratios) {
  128. let curq = document.querySelector(`#question-warper > div:nth-child(${current})`)
  129. if (curq.style.display == "none") return
  130. let _selector = optCountable.single + " .ws-radio"
  131. let optList = Array.from(curq.querySelectorAll(_selector))
  132. if (optList.length != ratios.length) {
  133. showMessage(`第${current}题的选项与比例长度不一致`)
  134. ratios = Array.from({ length: opts.length }, () => randint(1, 9));
  135. }
  136. let optIdx = singleRatio([0, optList.length - 1], ratios)
  137. optList[optIdx].click()
  138. }
  139.  
  140. async function _multiple(current, ratios) {
  141. let curq = document.querySelector(`#question-warper > div:nth-child(${current})`)
  142. if (curq.style.display == "none") return
  143. let _selector = optCountable.multiple + " .ws-checkbox"
  144. let opts = Array.from(curq.querySelectorAll(_selector))
  145. if (opts.length != ratios.length) {
  146. showMessage(`第${current}题的选项与比例长度不一致`)
  147. ratios = Array.from({ length: opts.length }, () => randint(9, 99));
  148. }
  149. let mul_list = [];
  150. while (mul_list.reduce((acc, curr) => acc + curr, 0) <= 0) {
  151. mul_list = ratios.map((item) => Math.random() < item / 100 ? 1 : 0)
  152. }
  153. for (const [index, item] of mul_list.entries()) {
  154. if (item == 1) {
  155. opts[index].click()
  156. }
  157. }
  158. }
  159.  
  160. async function _blank(current, texts) {
  161. let curq = document.querySelector(`#question-warper > div:nth-child(${current})`)
  162. if (curq.style.display == "none") return
  163. let textarea = curq.querySelector(optCountable.bank)
  164. textarea.value = texts[randint(0, texts.length - 1)]
  165. }
  166.  
  167. async function _scoreDefault(current, ratios) {
  168. let curq = document.querySelector(`#question-warper > div:nth-child(${current})`)
  169. if (curq.style.display == "none") return
  170. let _selector = optCountable.scoreDefault + " .circle-icon"
  171. let optList = Array.from(curq.querySelectorAll(_selector))
  172. if (optList.length != ratios.length) {
  173. showMessage(`第${current}题的选项与比例长度不一致`)
  174. ratios = Array.from({ length: optList.length }, () => randint(1, 9));
  175. }
  176. let optIdx = singleRatio([0, optList.length - 1], ratios)
  177. optList[optIdx].click()
  178. }
  179.  
  180. async function _matrixScale(current, ratios) {
  181.  
  182. let curq = document.querySelector(`#question-warper > div:nth-child(${current})`)
  183. if (curq.style.display == "none") return
  184. let qs_selector = optCountable.matrixScale.qs
  185. let qs = Array.from(curq.querySelectorAll(qs_selector))
  186. qs.forEach((qItem, index) => {
  187. let optList = Array.from(qItem.querySelectorAll(optCountable.matrixScale.opts + ">div"))
  188. let optIdx = singleRatio([0, optList.length - 1], ratios[index])
  189. optList[optIdx].click()
  190. })
  191.  
  192.  
  193. }
  194.  
  195. async function _star(current, ratios) {
  196. let curq = document.querySelector(`#question-warper > div:nth-child(${current})`)
  197. if (curq.style.display == "none") return
  198. let _selector = "div.q-score-default>div>div.icon-topic>div>div div.symbol"
  199. let optList = Array.from(curq.querySelectorAll(_selector))
  200. if (optList.length != ratios.length) {
  201. showMessage(`第${current}题的选项与比例长度不一致`)
  202. ratios = Array.from({ length: optList.length }, () => randint(1, 9));
  203. }
  204. let optIdx = singleRatio([0, optList.length - 1], ratios)
  205. optList[optIdx].click()
  206. }
  207.  
  208. async function _evaluation(current, ratios) {
  209. let curq = document.querySelector(`#question-warper > div:nth-child(${current})`)
  210. if (curq.style.display == "none") return
  211. let _selector = optCountable.evaluation + ">div"
  212. let optList = Array.from(curq.querySelectorAll(_selector))
  213. if (optList.length != ratios.length) {
  214. showMessage(`第${current}题的选项与比例长度不一致`)
  215. ratios = Array.from({ length: optList.length }, () => randint(1, 9));
  216. }
  217. let optIdx = singleRatio([0, optList.length - 1], ratios)
  218. optList[optIdx].click()
  219. }
  220.  
  221.  
  222.  
  223. async function submit() {
  224. await new Promise((resolve) => { setTimeout(resolve, 1500); });
  225. document.querySelector("#answer-submit-btn").click()
  226. }
  227.  
  228.  
  229. async function initial() {
  230. let qList = document.querySelectorAll('#question-warper > div');
  231. qList.forEach((qItem, qIdx) => {
  232. const qClass = qItem.querySelector("div.question-content").getAttribute("class");
  233. const setTitle = (type) => {
  234. const titleBox = qItem.querySelector("div.q-title-box .theme-text-wrap");
  235. titleBox.textContent = `${qIdx + 1}. ${type}-----` + titleBox.textContent;
  236. };
  237. if (qClass.includes("single")) {
  238. setTitle("single");
  239. } else if (qClass.includes("multiple")) {
  240. setTitle("multiple");
  241. } else if (qClass.includes("bank")) {
  242. setTitle("blank");
  243. } else if (qClass.includes("evaluation")) {
  244. setTitle("evaluation");
  245. } else if (qClass.includes("q-score-default")) {
  246. if (qItem.querySelector(".symbol-item-wrap")) { setTitle("star"); }
  247. else { setTitle("scoreDefault"); }
  248. } else if (qClass.includes("matrix-scale")) {
  249. setTitle("matrixScale");
  250. } else {
  251. setTitle("unkown");
  252. }
  253. });
  254. await sleep(1)
  255. }
  256.  
  257.  
  258. (async function () {
  259. 'use strict';
  260. clearAll();
  261. await sleep(2);
  262. while (!document.querySelector("#answer-submit-btn")) {
  263. await sleep(2)
  264. if (checkAgain()) {
  265. clickAgain();
  266. }
  267. await sleep(2)
  268. location.reload(true);
  269. }
  270. const single = safeExecute(_single);
  271. const multiple = safeExecute(_multiple);
  272. const blank = safeExecute(_blank);
  273. const scoreDefault = safeExecute(_scoreDefault);
  274. const matrixScale = safeExecute(_matrixScale);
  275. const star = safeExecute(_star);
  276. const evaluation = safeExecute(_evaluation);
  277. clickContinue()
  278. clickRestart()
  279. initial()
  280. // 仅需关注这里的配置即可
  281. const q11es = [
  282. // 以下代码针对示例问卷:https://www.wenjuan.com/s/UZBZJv1Y6b
  283. // 第一题(单选题,应该调用single)的脚本未写,留给用户自己编辑,以熟悉此脚本的用法
  284. () => multiple(2, [50, 50, 50, 50]), // 多选题
  285. () => blank(3, ["关注公众号", "做实验的研究牲"]),// 填空题
  286. () => scoreDefault(4, [1, 2, 3, 4, 3]), // 评分题
  287. () => scoreDefault(5, [1, 2, 4, 3]), // 评分题,此题比例故意写错(对示例问卷而言),以展示脚本的容错能力
  288. () => matrixScale(6, [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]]), // 矩阵评分题
  289. () => scoreDefault(7, [1, 2, 3, 4, 3]),
  290. () => scoreDefault(8, [1, 2, 3, 4, 3]),
  291. () => scoreDefault(9, [1, 2, 3, 4, 3]),
  292. () => star(10, [1, 2, 3, 4, 3]), // 星级题
  293. () => evaluation(11, [1, 2, 3, 4, 3]), // 评价题
  294. () => scoreDefault(12, [1, 2, 3, 4, 3, 2, 1, 2, 3, 4, 5]),
  295. () => matrixScale(13, [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]]),
  296. ];
  297. for (let q11e of q11es) {
  298. await q11e();
  299. await sleep(1) // 等待1s后继续下一题
  300. }
  301. await sleep(2) // 等待2s后提交
  302. await submit();
  303. clearAll();
  304. setTimeout(location.reload(true), 2.5 * 1000)
  305. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址