学习通考试自动搜题
小祁同学 2022-12-12
技术文章
Elasticsearch
python
功能
电脑端学习通考试自动搜索题目,获取自己题库中最相似的答案
要求
需要自己有题库哦,可以参考上篇博客搭建Elasticsearch
介绍
这里介绍油猴插件和python使用flask搭建的搜题接口,以及上传题库json
# 从word导入题库
老师发的是word文档,使用Python的docx库来读取word,做一些判断使word中的内容转换为json
es字段如下:
Python对word的处理很快,也特别的简单,这里浅淡的看一下我写的Python代码,word内容转对应的json
from docx import Document
from copy import deepcopy
# 打开word文档
document = Document("你的Word.docx")
# 获取所有段落
all_paragraphs = document.paragraphs
# 题库列表
timu = []
# 题目,答案
ti_daan = {}
# 行号
for index in range(0,len(all_paragraphs)):
paragraph = all_paragraphs[index]
hang = paragraph.text
if "答案" in hang:
ti_daan["answer"] = hang
a = all_paragraphs[index - 4].text
b = all_paragraphs[index - 3].text
c = all_paragraphs[index - 2].text
d = all_paragraphs[index - 1].text
ti_daan["choose"] = a,b,c,d
timu.append(deepcopy(ti_daan))
elif "(" in hang or "(" in hang:
str1=str(hang)
if "." in str1:
s = str1.split(".")
else:
s = str1.split(".")
ti_daan["timu"] = s[1].replace(" ","")
print(timu)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
最后控制台输出一个列表,列表中是字典即json,你要根据实际情况去改变代码。
# 将json数据写入Elasticsearch数据库中
安装Elasticsearch第三方包
你的Elasticsearch数据库是什么版本,那么Python的第三方包就是什么版本,保持版本的相同
pip install elasticsearch==7.6.1
1
#coding=utf-8
from elasticsearch import Elasticsearch
import elasticsearch.helpers
# 你的es数据库ip
es = Elasticsearch("http://ip:9200/")
# json数据,存放在datas列表中
datas = []
actions = [
{
'_op_type': 'index',
# xxt_doc修改你的索引,相当于mysql中的库
'_index': "xxt_doc",
# js修改你的类别,相当于mysql中的表
'_type': "js",
# 内容
'_source': d
}
for d in datas
]
# 使用bulk批量添加,速度很快很快
elasticsearch.helpers.bulk( es, actions )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 使用Python作为后端代理es的api
这里使用Python的flask,调用es数据库
如果你不了解,你可以参考另一篇博客,flask入门教程
from flask import Flask
from flask import request
from elasticsearch import Elasticsearch
# 开发api
app = Flask(__name__)
es = Elasticsearch("http://43.143.251.186:9200/")
# get请求
@app.route('/gsql', methods=['GET'])
def fun_getsql():
timu = request.values.get('timu')
if timu is None:
return
timu = timu.replace("%20", "")
timu = timu.replace("(选择题)", "")
timu = timu.replace("(判断题)", "")
timu = timu.replace("(填空题)", "")
result = es.search(index='xxt_doc', doc_type='mysql', body={"query": {"match": {"timu": timu}}})
return result
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5002)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 油猴自动搜索
- 填空题保存按钮,功能不太完善,每点一次“填空题保存按钮显示”,保存按钮将会从第一题开始,依次显示,点击一次,显示一个
- 如果es题库中的匹配率大于20,那么该题背景是绿色的,否则是白色的
- 点击小窗口中题目自动复制,解决了学习通不让复制的限制
- 使用时,需要整卷预览,整卷预览才会触发该脚本
界面如下:
// ==UserScript==
// @name 超星考试仅考试-es题库
// @namespace 仅考试
// @version 3.0.3
// @description 自动搜索
// @author qiz
// @match *://*.chaoxing.com/exam-ans/mooc2/exam/preview*
// @connect esxxt.goodluckweb.top
// @connect localhost
// @run-at document-end
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @license MIT
// ==/UserScript==
// 设置修改后,需要刷新或重新打开网课页面才会生效
var setting = {
// 5E3 == 5000,科学记数法,表示毫秒数
time: 5E3 // 默认响应速度为5秒,不建议小于3秒
// 1代表开启,0代表关闭
, none: 0 // 未找到答案或无匹配答案时执行默认操作,默认关闭
, jump: 0 // 答题完成后自动切换,默认关闭
, copy: 0 // 自动复制答案到剪贴板,也可以通过手动点击按钮或答案进行复制,默认关闭
// 非自动化操作
, hide: 0 // 不加载答案搜索提示框,键盘↑和↓可以临时移除和加载,默认关闭
, scale: 0 // 富文本编辑器高度自动拉伸,用于文本类题目,答题框根据内容自动调整大小,默认关闭
, over: 0 // 延长答题时间,避免超时自动交卷,默认关闭
, log: 0 // 屏蔽答题日志上传,教师端监考无痕,可用于配合over参数,默认关闭
},
_self = unsafeWindow,
$ = _self.jQuery,
UE = _self.UE;
String.prototype.toCDB = function () {
return this.replace(/\s/g, '').replace(/[\uff01-\uff5e]/g, function (str) {
return String.fromCharCode(str.charCodeAt(0) - 65248);
}).replace(/[“”]/g, '"').replace(/[‘’]/g, "'").replace(/。/g, '.');
};
var indexTi = 0;
// 修改这里.mark_name div,题在目录
setting.timulist = filterImg('.mark_name');
setting.TiMu = [
"0",
// filterImg('.Cy_TItle .clearfix').replace(/\s*(\d+\.\d+分)$/, ''),
$('[name^=type]:not([id])').val() || '-1',
$('.cur a').text().trim() || '无',
$('li .clearfix').map(function () {
return filterImg(this);
})
];
// 填空题保存按钮,这里不太完善,每点一次“填空题保存按钮显示”,保存按钮将会从第一题开始,依次显示,点击一次,显示一个
var save_list = document.getElementsByClassName('saveButtonClass');
var save_list_index = 0;
// console.log(setting.TiMu);
setting.div = $(
'<div class="win_xxt_select" id="win_xxt_select" style="border: 2px dashed rgb(0, 85, 68); width: 430px; position: fixed; bottom: 0; right: 0; z-index: 99999; background-color: rgba(70, 196, 38, 0.6);height:500px; overflow-x: auto;">' +
'<span style="font-size: medium;position: fixed;z-index: 999991;"></span>' +
'<div style="font-size: medium;position: fixed;z-index: 999991;background-color: yellow;">正在搜索答案...</div>' +
'<button id="enddati" style="margin-right: 10px;position: fixed;z-index: 999991;margin-left: 115px;">暂停答题</button>' +
'<button style="margin-right: 10px;' + (setting.jump ? '' : ' display: none;') + '">点击停止本次切换</button>' +
'<button style="margin-right: 10px;position: fixed;z-index: 999991;margin-left: 200px;">重新查询</button>' +
'<button style="margin-right: 10px; display: none;">复制答案</button>' +
// '<button>答题详情</button>' +
'<button style="margin-right: 10px;position: fixed;z-index: 999991;margin-left: 270px;">填空题保存按钮显示</button>' +
'<div style="max-height: 200px;margin-top:50px;">' +
'<table border="1" style="font-size: 12px;">' +
'<thead>' +
'<tr>' +
'<th colspan="4">' + ($('#randomOptions').val() == 'true' ? '<font color="red">本次考试的选项为乱序 脚本已将选项重新标注 点击题目或答案都可以复制</font>' : '') + '</th>' +
'</tr>' +
'<tr>' +
'<th style="width: 60%; min-width: 50px;">题目</th>' +
'<th style="min-width: 80px;">库中的题目</th>' +
'<th style="min-width: 130px;">选项</th>' +
'<th style="min-width: 100px;">答案</th>' +
'</tr>' +
'</thead>' +
'<tfoot style="' + (setting.jump ? 'display: none;' : '') + '">' +
'<tr>' +
'<th colspan="3">已关闭 本次自动切换</th>' +
'</tr>' +
'</tfoot>' +
'<tbody>' +
'<tr>' +
'<td colspan="3" style="display: none;"></td>' +
'</tr>' +
'</tbody>' +
'</table>' +
'</div>' +
'</div>'
).appendTo('body').on('click', 'button, td', function () {
var num = setting.$btn.index(this);
// num上面按钮的编号
if (num == -1) {
GM_setClipboard($(this).text());
} else if (num === 0) {
if (setting.loop) {
clearInterval(setting.loop);
delete setting.loop;
num = ['已暂停搜索', '继续答题'];
} else {
setting.loop = setInterval(findTiMu, setting.time);
num = ['正在搜索答案...', '暂停答题'];
}
setting.$div.html(function () {
return $(this).data('html') || num[0];
}).removeData('html');
$(this).html(num[1]);
} else if (num == 1) {
setting.jump = 0;
setting.$div.html(function () {
return arguments[1].replace('即将切换下一题', '未开启自动切换');
});
setting.div.find('tfoot').add(this).toggle();
} else if (num == 2) {
location.reload();
} else if (num == 3) {
GM_setClipboard(setting.div.find('td:last').text());
} else if (num == 4) {
if (save_list_index < save_list.length && save_list_index != 0) {
save_list[save_list_index - 1].style.display = "none";
save_list[save_list_index].style.display = "block";
save_list_index++;
} else {
save_list_index = 0;
save_list[0].style.display = "block";
save_list[save_list.length - 1].style.display = "none";
save_list_index++;
}
}
}).detach(setting.hide ? '*' : 'html');
setting.$btn = setting.div.children('button');
setting.$div = setting.div.children('div:eq(0)');
$(document).keydown(function (event) {
if (event.keyCode == 38) {
setting.div.detach();
} else if (event.keyCode == 40) {
setting.div.appendTo('body');
}
});
if (setting.over) _self.maxtime = 36E3;
if (setting.log) _self.pushExamMonitorLog = $.noop;
if (setting.scale) _self.UEDITOR_CONFIG.scaleEnabled = false;
$.each(UE.instants, function () {
var key = this.key;
this.ready(function () {
this.destroy();
UE.getEditor(key);
});
});
$('.Cy_ulBottom input').each(function (index) {
if (setting.TiMu[1] > 1) return;
$(this).parent().contents().eq(1).replaceWith(' ' + this.value);
$('.Cy_ulTop i').eq(index).text(this.value + '、');
});
setting.loop = setInterval(findTiMu, setting.time);
function findTiMu() {
setting.timulist[indexTi] = setting.timulist[indexTi].replace("(单选题)", "")
setting.TiMu[0] = setting.timulist[indexTi];
let timu_str = setting.timulist[indexTi];
indexTi++;
GM_xmlhttpRequest({
method: 'GET',
url: 'http://localhost:5002/gsql?timu=' + encodeURIComponent(timu_str),
headers: {
'Content-type': 'application/json;charset=UTF-8'
},
timeout: setting.time,
onload: function (xhr) {
if (!setting.loop) {
} else if (xhr.status == 200) {
var obj = $.parseJSON(xhr.responseText);
var result = obj.hits.hits[0]._source.choose + "\n" + obj.hits.hits[0]._source.answer;
obj.data = obj.hits.hits[0]._source.answer;
obj.opt = obj.hits.hits[0]._source.choose;
obj.code = 1;
// if (obj.code) {
var data = String(obj.data).replace(/&/g, '&').replace(/<(?!img)/g, '<'),
que = setting.TiMu[0].match('<img') ? setting.TiMu[0] : setting.TiMu[0].replace(/&/g, '&').replace(/</g, '<');
obj.data = /^http/.test(data) ? '<img src="' + obj.data + '">' : obj.data;
let choose_list = obj.hits.hits[0]._source.choose;
let choose_str = "";
if (choose_list.length > 0) {
for (let choose_index = 0; choose_index < choose_list.length; choose_index++) {
choose_str = choose_str + choose_list[choose_index] + '</br>';
}
} else {
choose_str = choose_list;
}
// 题库该题匹配率
var score = obj.hits.hits[0]._score;
// 根据后端传来的_score判断题目匹配率,匹配率大于20,基本就是一样的题,如果小于20,那么这道题在学习通中显示的时候,背景是白色的
if (score>20)
{
setting.div.find('tbody').append(
'<tr>' +
'<td title="点击可复制">' + que + '</td>' +
'<td title="点击可复制">' + obj.hits.hits[0]._source.timu + '</td>' +
'<td title="点击可复制">' + choose_str + '</td>' +
'<td title="点击可复制">' + obj.hits.hits[0]._source.answer + '</td>' +
'</tr>'
);
}else{
setting.div.find('tbody').append(
'<tr>' +
'<td title="点击可复制" style="background-color: white">' + que + '</td>' +
'<td title="点击可复制" style="background-color: white">' + obj.hits.hits[0]._source.timu + '</td>' +
'<td title="点击可复制" style="background-color: white">' + choose_str + '</td>' +
'<td title="点击可复制" style="background-color: white">' + obj.hits.hits[0]._source.answer + '</td>' +
'</tr>'
);
}
// 搜索完了,暂停搜索
if (indexTi >= setting.timulist.length) {
document.getElementById("enddati").click();
}
setting.div.children('span').html(obj.msg || '');
} else if (xhr.status == 403) {
var html = xhr.responseText.indexOf('{') ? '请求过于频繁,建议稍后再试' : $.parseJSON(xhr.responseText).data;
setting.$div.data('html', html).siblings('button:eq(0)').click();
} else {
setting.$div.text('服务器异常,正在重试...');
}
},
ontimeout: function () {
setting.loop && setting.$div.text('服务器超时,正在重试...');
}
});
}
// 调整位置
let box = document.getElementById("win_xxt_select");
box.onmousedown = function (event) {
let disx = event.clientX - box.offsetLeft;
let disy = event.clientY - box.offsetTop;
//此处不是box.onmousemove,是document.onmousemove
document.onmousemove = function (event) {
box.style.left = event.clientX - disx + 'px';
box.style.top = event.clientY - disy + 'px';
}
//要写在ommousedown里面
document.onmouseup = function () {
//这俩都要置为null
document.onmousemove = null;
document.onmouseup = null;
return false;
}
}
function filterImg(dom) {
let list = [];
for (let i = 0; i < $(dom).length; i++) {
list.push($(dom)[i].outerText);
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261