- // ==UserScript==
- // @name Nowcoder Quiz Snatcher
- // @namespace https://www.nowcoder.com/
- // @version 0.1
- // @description 自动化抓取牛客网上一些免费题库,并以CSV格式导出下载
- // @author Yifei Zhow
- // @match https://www.nowcoder.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
- // @grant none
- // @license MIT
- // ==/UserScript==
-
- // 以此页面举例:https://www.nowcoder.com/exam/intelligent?questionJobId=2&tagId=21029
- const gKeywords = ['用户研究','原型设计','数据分析','产品常识','产品设计','产品规划','需求分析','竞品研究','文档撰写'];
- // 针对某一题库,每次点击出题具有随机性,抓取到达一定的阈值后停止
- const gThresholdLvl = 85;
-
- /* JSON Object looks like:
- {"用户研究":{"claim_to_have": 56, "already_grabbed": 30, content: [{
- "id": 1261504920, "type": "单选题", "info": "题干", "options": ["A.甲甲甲","B.乙乙乙","C.丙丙丙","D.丁丁丁"]
- }, ..., {...}]}}
- */
- const kNowWorkingKey = 'TM_NOWCODER_WORKING_KEY';
- const kQuestionDictKey = 'TM_NOWCODER_QUESTION_DICT_KEY';
- const kClaimToHave = 'claim_to_have';
- const kAlreadyGrabbed = 'already_grabbed';
-
-
- // Utilities
- function getLocation(href) {
- var match = href.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
- return match && {
- href: href,
- protocol: match[1],
- host: match[2],
- hostname: match[3],
- port: match[4],
- pathname: match[5],
- search: match[6],
- hash: match[7]
- }
- }
-
- function hashCode(s) {
- for(var i = 0, h = 0; i < s.length; i++)
- h = Math.imul(31, h) + s.charCodeAt(i) | 0;
- return h;
- }
-
- function exportToCsv(filename, rows) {
- var processRow = function (row) {
- var finalVal = '';
- if (!Array.isArray(row)) {
- // Mapping
- const mappedRow = [row['id'], row['type'], row['info']];
- Array.prototype.forEach.call(row['options'], function(el) {
- mappedRow.push(el);
- });
- row = mappedRow; // Deep copy preferred
- }
- for (var j = 0; j < row.length; j++) {
- var innerValue = row[j] === null ? '' : row[j].toString();
- if (row[j] instanceof Date) {
- innerValue = row[j].toLocaleString();
- };
- var result = innerValue.replace(/"/g, '""');
- if (result.search(/("|,|\n)/g) >= 0)
- result = '"' + result + '"';
- if (j > 0)
- finalVal += ',';
- finalVal += result;
- }
- return finalVal + '\n';
- };
-
- var csvFile = '';
- for (var i = 0; i < rows.length; i++) {
- csvFile += processRow(rows[i]);
- }
-
- var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' });
- if (navigator.msSaveBlob) { // IE 10+
- navigator.msSaveBlob(blob, filename);
- } else {
- var link = document.createElement("a");
- if (link.download !== undefined) { // feature detection
- // Browsers that support HTML5 download attribute
- var url = URL.createObjectURL(blob);
- link.setAttribute("href", url);
- link.setAttribute("download", filename);
- link.style.visibility = 'hidden';
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- }
- }
- }
-
- // Sub-process
- function storeQuestionIfNecessary(key, id, type, info, options = []) {
- let dict;
- try {
- dict = JSON.parse(window.localStorage.getItem(kQuestionDictKey) || '{}');
- } catch (error) {
- // JSON parse error!
- dict = {};
- }
- const keyDict = dict[key] || {};
- const keyContent = keyDict['content'] || [];
- for (let c of keyContent) {
- if (c['id'] == id) { return false; }
- }
- keyContent.push({
- id,
- type,
- info,
- options
- });
- // Write-back
- keyDict['content'] = keyContent;
- keyDict[kAlreadyGrabbed] = (keyDict[kAlreadyGrabbed] || 0) + 1;
- dict[key] = keyDict;
- window.localStorage.setItem(kQuestionDictKey, JSON.stringify(dict));
- return true;
- }
-
- function willJumpToNextWords(workingKey) {
- let dict;
- try {
- dict = JSON.parse(window.localStorage.getItem(kQuestionDictKey) || '{}');
- } catch (error) {
- return { 'next': false, 'final-word': false };
- }
- const keyDict = dict[workingKey] || {};
- const total = keyDict[kClaimToHave] || 0;
- const portion = keyDict[kAlreadyGrabbed] || 0;
- if (total == 0) {
- console.err('Now working percent: ', 'Unable to calculate.');
- alert('Total count missing!');
- return { 'next': false, 'final-word': false };
- } else {
- console.log('Now working percent: ', ((portion/total) * 100).toFixed(2) + '%');
- }
-
- if ((portion/total) * 100 > gThresholdLvl) {
- // Reach Threshold Level
- var idx = gKeywords.indexOf(workingKey);
- idx += 1;
- if (idx < gKeywords.length) {
- // Has next word
- workingKey = gKeywords[idx];
- window.localStorage.setItem(kNowWorkingKey, workingKey);
- return { 'next': true, 'final-word': false };
- } else {
- // Current is final word
- return { 'next': false, 'final-word': true };
- }
- } else {
- // Not reached
- return { 'next': false, 'final-word': false };
- }
- }
-
- function init() {
- let key = window.localStorage.getItem(kNowWorkingKey);
- if (!key) {
- key = gKeywords[0];
- }
- window.localStorage.setItem(kNowWorkingKey, key);
- }
-
- function reset() {
- window.localStorage.removeItem(kNowWorkingKey);
- window.localStorage.removeItem(kQuestionDictKey);
- }
-
- function storeClaim(workingKey, claim) {
- let dict;
- try {
- dict = JSON.parse(window.localStorage.getItem(kQuestionDictKey) || '{}');
- } catch (error) {
- return false;
- }
- const keyDict = dict[workingKey] || {};
- const total = keyDict[kClaimToHave];
- if (!total || total < 0 || total < claim) {
- // Write-back
- keyDict[kClaimToHave] = claim;
- dict[workingKey] = keyDict;
- window.localStorage.setItem(kQuestionDictKey, JSON.stringify(dict));
- }
- }
- // Main
- (function() {
- 'use strict';
- init();
-
- const h = getLocation(window.location.href);
- const workingKey = window.localStorage.getItem(kNowWorkingKey);
-
- let els;
- if (!h || h.hostname != "www.nowcoder.com") return;
- if (h.pathname == "/exam/intelligent") {
- // Index Page
- els = document.getElementsByClassName("exercise-card") || [];
- Array.prototype.forEach.call(els, function(el) {
- const title = el.querySelector(".exercises-card-title").innerText;
- const score = el.querySelector(".tw-text-gray-500").innerText;
- if (title.indexOf(workingKey) != -1) {
- // Extract kClaimToHave and Save
- const mth = score.match(/已做(\d+)\/(\d+)题/);
- if (mth && mth[2]) {
- storeClaim(workingKey, parseInt(mth[2], 10));
- setTimeout(function() {
- el.click();
- }, 2000);
- }
- }
- });
- } else if (h.pathname.indexOf('/exam/test/') != -1) {
- els = document.querySelectorAll(".test-paper .paper-question");
- Array.prototype.forEach.call(els, function(el) {
- const qType = el.querySelector(".question-desc-header").innerText;
- const qInfo = el.querySelector(".question-info").innerText;
- let qOptions = [];
- Array.prototype.forEach.call(el.querySelectorAll(".question-select .option-item"), function(e) { qOptions.push(e.innerText); });
- const qID = hashCode(qInfo);
- const isStored = storeQuestionIfNecessary(workingKey, qID, qType, qInfo, qOptions);
- console.log('Is Stored: ', isStored, qType);
- });
- // Exam Page
- let result = willJumpToNextWords(workingKey);
- if (result['final-word']) {
- if (confirm("Scnatched completed, export as CSV?")) {
- var dict = JSON.parse(window.localStorage.getItem(kQuestionDictKey));
- Array.prototype.forEach.call(gKeywords, function(k) {
- exportToCsv(k+'.csv', dict[k]['content']);
- });
- // reset();
- }
- } else {
- setTimeout(function() {
- history.back();
- }, 2000);
- }
- }
- })();