Greasy Fork is available in English.

Nowcoder Quiz Snatcher

自动化抓取牛客网上一些免费题库,并以CSV格式导出下载

  1. // ==UserScript==
  2. // @name Nowcoder Quiz Snatcher
  3. // @namespace https://www.nowcoder.com/
  4. // @version 0.1
  5. // @description 自动化抓取牛客网上一些免费题库,并以CSV格式导出下载
  6. // @author Yifei Zhow
  7. // @match https://www.nowcoder.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. // 以此页面举例:https://www.nowcoder.com/exam/intelligent?questionJobId=2&tagId=21029
  14. const gKeywords = ['用户研究','原型设计','数据分析','产品常识','产品设计','产品规划','需求分析','竞品研究','文档撰写'];
  15. // 针对某一题库,每次点击出题具有随机性,抓取到达一定的阈值后停止
  16. const gThresholdLvl = 85;
  17.  
  18. /* JSON Object looks like:
  19. {"用户研究":{"claim_to_have": 56, "already_grabbed": 30, content: [{
  20. "id": 1261504920, "type": "单选题", "info": "题干", "options": ["A.甲甲甲","B.乙乙乙","C.丙丙丙","D.丁丁丁"]
  21. }, ..., {...}]}}
  22. */
  23. const kNowWorkingKey = 'TM_NOWCODER_WORKING_KEY';
  24. const kQuestionDictKey = 'TM_NOWCODER_QUESTION_DICT_KEY';
  25. const kClaimToHave = 'claim_to_have';
  26. const kAlreadyGrabbed = 'already_grabbed';
  27.  
  28.  
  29. // Utilities
  30. function getLocation(href) {
  31. var match = href.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
  32. return match && {
  33. href: href,
  34. protocol: match[1],
  35. host: match[2],
  36. hostname: match[3],
  37. port: match[4],
  38. pathname: match[5],
  39. search: match[6],
  40. hash: match[7]
  41. }
  42. }
  43.  
  44. function hashCode(s) {
  45. for(var i = 0, h = 0; i < s.length; i++)
  46. h = Math.imul(31, h) + s.charCodeAt(i) | 0;
  47. return h;
  48. }
  49.  
  50. function exportToCsv(filename, rows) {
  51. var processRow = function (row) {
  52. var finalVal = '';
  53. if (!Array.isArray(row)) {
  54. // Mapping
  55. const mappedRow = [row['id'], row['type'], row['info']];
  56. Array.prototype.forEach.call(row['options'], function(el) {
  57. mappedRow.push(el);
  58. });
  59. row = mappedRow; // Deep copy preferred
  60. }
  61. for (var j = 0; j < row.length; j++) {
  62. var innerValue = row[j] === null ? '' : row[j].toString();
  63. if (row[j] instanceof Date) {
  64. innerValue = row[j].toLocaleString();
  65. };
  66. var result = innerValue.replace(/"/g, '""');
  67. if (result.search(/("|,|\n)/g) >= 0)
  68. result = '"' + result + '"';
  69. if (j > 0)
  70. finalVal += ',';
  71. finalVal += result;
  72. }
  73. return finalVal + '\n';
  74. };
  75.  
  76. var csvFile = '';
  77. for (var i = 0; i < rows.length; i++) {
  78. csvFile += processRow(rows[i]);
  79. }
  80.  
  81. var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' });
  82. if (navigator.msSaveBlob) { // IE 10+
  83. navigator.msSaveBlob(blob, filename);
  84. } else {
  85. var link = document.createElement("a");
  86. if (link.download !== undefined) { // feature detection
  87. // Browsers that support HTML5 download attribute
  88. var url = URL.createObjectURL(blob);
  89. link.setAttribute("href", url);
  90. link.setAttribute("download", filename);
  91. link.style.visibility = 'hidden';
  92. document.body.appendChild(link);
  93. link.click();
  94. document.body.removeChild(link);
  95. }
  96. }
  97. }
  98.  
  99. // Sub-process
  100. function storeQuestionIfNecessary(key, id, type, info, options = []) {
  101. let dict;
  102. try {
  103. dict = JSON.parse(window.localStorage.getItem(kQuestionDictKey) || '{}');
  104. } catch (error) {
  105. // JSON parse error!
  106. dict = {};
  107. }
  108. const keyDict = dict[key] || {};
  109. const keyContent = keyDict['content'] || [];
  110. for (let c of keyContent) {
  111. if (c['id'] == id) { return false; }
  112. }
  113. keyContent.push({
  114. id,
  115. type,
  116. info,
  117. options
  118. });
  119. // Write-back
  120. keyDict['content'] = keyContent;
  121. keyDict[kAlreadyGrabbed] = (keyDict[kAlreadyGrabbed] || 0) + 1;
  122. dict[key] = keyDict;
  123. window.localStorage.setItem(kQuestionDictKey, JSON.stringify(dict));
  124. return true;
  125. }
  126.  
  127. function willJumpToNextWords(workingKey) {
  128. let dict;
  129. try {
  130. dict = JSON.parse(window.localStorage.getItem(kQuestionDictKey) || '{}');
  131. } catch (error) {
  132. return { 'next': false, 'final-word': false };
  133. }
  134. const keyDict = dict[workingKey] || {};
  135. const total = keyDict[kClaimToHave] || 0;
  136. const portion = keyDict[kAlreadyGrabbed] || 0;
  137. if (total == 0) {
  138. console.err('Now working percent: ', 'Unable to calculate.');
  139. alert('Total count missing!');
  140. return { 'next': false, 'final-word': false };
  141. } else {
  142. console.log('Now working percent: ', ((portion/total) * 100).toFixed(2) + '%');
  143. }
  144.  
  145. if ((portion/total) * 100 > gThresholdLvl) {
  146. // Reach Threshold Level
  147. var idx = gKeywords.indexOf(workingKey);
  148. idx += 1;
  149. if (idx < gKeywords.length) {
  150. // Has next word
  151. workingKey = gKeywords[idx];
  152. window.localStorage.setItem(kNowWorkingKey, workingKey);
  153. return { 'next': true, 'final-word': false };
  154. } else {
  155. // Current is final word
  156. return { 'next': false, 'final-word': true };
  157. }
  158. } else {
  159. // Not reached
  160. return { 'next': false, 'final-word': false };
  161. }
  162. }
  163.  
  164. function init() {
  165. let key = window.localStorage.getItem(kNowWorkingKey);
  166. if (!key) {
  167. key = gKeywords[0];
  168. }
  169. window.localStorage.setItem(kNowWorkingKey, key);
  170. }
  171.  
  172. function reset() {
  173. window.localStorage.removeItem(kNowWorkingKey);
  174. window.localStorage.removeItem(kQuestionDictKey);
  175. }
  176.  
  177. function storeClaim(workingKey, claim) {
  178. let dict;
  179. try {
  180. dict = JSON.parse(window.localStorage.getItem(kQuestionDictKey) || '{}');
  181. } catch (error) {
  182. return false;
  183. }
  184. const keyDict = dict[workingKey] || {};
  185. const total = keyDict[kClaimToHave];
  186. if (!total || total < 0 || total < claim) {
  187. // Write-back
  188. keyDict[kClaimToHave] = claim;
  189. dict[workingKey] = keyDict;
  190. window.localStorage.setItem(kQuestionDictKey, JSON.stringify(dict));
  191. }
  192. }
  193. // Main
  194. (function() {
  195. 'use strict';
  196. init();
  197.  
  198. const h = getLocation(window.location.href);
  199. const workingKey = window.localStorage.getItem(kNowWorkingKey);
  200.  
  201. let els;
  202. if (!h || h.hostname != "www.nowcoder.com") return;
  203. if (h.pathname == "/exam/intelligent") {
  204. // Index Page
  205. els = document.getElementsByClassName("exercise-card") || [];
  206. Array.prototype.forEach.call(els, function(el) {
  207. const title = el.querySelector(".exercises-card-title").innerText;
  208. const score = el.querySelector(".tw-text-gray-500").innerText;
  209. if (title.indexOf(workingKey) != -1) {
  210. // Extract kClaimToHave and Save
  211. const mth = score.match(/已做(\d+)\/(\d+)题/);
  212. if (mth && mth[2]) {
  213. storeClaim(workingKey, parseInt(mth[2], 10));
  214. setTimeout(function() {
  215. el.click();
  216. }, 2000);
  217. }
  218. }
  219. });
  220. } else if (h.pathname.indexOf('/exam/test/') != -1) {
  221. els = document.querySelectorAll(".test-paper .paper-question");
  222. Array.prototype.forEach.call(els, function(el) {
  223. const qType = el.querySelector(".question-desc-header").innerText;
  224. const qInfo = el.querySelector(".question-info").innerText;
  225. let qOptions = [];
  226. Array.prototype.forEach.call(el.querySelectorAll(".question-select .option-item"), function(e) { qOptions.push(e.innerText); });
  227. const qID = hashCode(qInfo);
  228. const isStored = storeQuestionIfNecessary(workingKey, qID, qType, qInfo, qOptions);
  229. console.log('Is Stored: ', isStored, qType);
  230. });
  231. // Exam Page
  232. let result = willJumpToNextWords(workingKey);
  233. if (result['final-word']) {
  234. if (confirm("Scnatched completed, export as CSV?")) {
  235. var dict = JSON.parse(window.localStorage.getItem(kQuestionDictKey));
  236. Array.prototype.forEach.call(gKeywords, function(k) {
  237. exportToCsv(k+'.csv', dict[k]['content']);
  238. });
  239. // reset();
  240. }
  241. } else {
  242. setTimeout(function() {
  243. history.back();
  244. }, 2000);
  245. }
  246. }
  247. })();