周口市专业技术人员继续教育刷课脚本分享
# 平台情况
嗯...周口这边的朋友应该都知道这个平台,周口市专业技术人员继续教育公共服务平台,网址 http://zkzj.jxjyedu.org.cn/2019/index.jsp 。是周口市继续教育协会搞的,地址就在八一路南段那边,人事考试中心院里。
上个月周口淮阳的老李给我打电话,说他们单位要求在这个平台上刷公需课。老李在淮阳一个事业单位干了好多年了,平时工作就是写材料、跑审批、开会,忙得不行。他说这个平台的课吧,内容倒也还行,什么职称制度改革、专业技术人员继续教育规定之类的,但就是视频太长了,一门课好几个小时,而且必须完整看完才能算学时。
老李跟我抱怨说,他上周连着三天晚上坐在电脑前刷课,眼睛酸得不行。白天上班已经盯了一天屏幕了,回家还得继续盯,关键视频播完不会自动跳下一节,得手动点,有时候忘了点就停在那儿白等。还有那个长时间不操作就掉线的问题,特别烦人。
我打开这个平台看了一下,界面比较朴素,典型的政务网站风格。课程列表在左侧,点进去有章节目录。视频播放器就是普通的那种,进度条能看但不太灵敏。说实话这个平台真的挺麻烦的,各种小问题不少。
# 脚本功能
提示
如需代学,请联系客服,支持闲鱼交易。

微信联系:yizhituziang

QQ联系:2422270452
- img: /img/weixin.jpg
name: 微信联系:yizhituziang
- img: /img/qq.jpg
name: QQ联系:2422270452
针对周口市专业技术人员继续教育平台的特点,脚本做了这些功能:
视频自动播放,进到课程页面不用手动点播放。自动切换下一章节,视频快播完的时候自动跳转。防掉线模拟,隔一段时间模拟鼠标移动,避免系统判定长时间无操作。倍速调节,1倍到2倍速都可以选。进度统计,面板上能看到已完成多少节课。
脚本安装地址: 暂时下架
# 使用感受
老李用了差不多两周了,他说比之前纯手动刷省心多了。现在他上班前把浏览器打开挂着,中午休息的时候看一眼进度,下班的时候基本一天的任务就跑完了。控制面板在右边,状态、已完成数量、倍速都显示得挺清楚的。
有个事得说一下,这个平台偶尔会弹验证框,脚本遇到这种情况会暂停等你处理。还有个别课程有课后作业或者测试,这个得自己做,脚本帮不了。另外如果你们单位要求人脸识别验证,那还是得自己来。
对了,老李还跟我说了个事,他同事小周也用这个脚本,但是小周用的360浏览器,有时候会卡。建议用Chrome或者Edge,兼容性最好。
# 使用场景
工作忙没时间盯屏幕的,像老李那样白天一堆事晚上还要加班的。课程内容之前就学过的,走个流程拿学时就行。想早点刷完早点省心的,开个1.5倍速挂着自动跑。
# 技术细节
周口这个平台用的是比较传统的在线教育框架,播放器兼容性一般。脚本通过定时检测video元素状态来判断播放进度,然后配合课程列表的DOM结构找到下一节。
防掉线这块比较关键,因为周口这个平台对长时间不操作检测比较敏感。脚本会随机生成鼠标移动轨迹,间隔时间也做了随机化处理,不会太规律被系统发现。
整体方案针对周口平台做了专门适配,老李用了两周没出过什么大问题。
# 常见问题
脚本安装地址暂时下架,有需要代学的朋友看页面底部联系方式。
倍速开多少合适?建议1.5倍,周口这边服务器有时候响应慢,太快了怕加载跟不上。
用什么浏览器?Chrome或Edge最稳,360要开极速模式。
进度没同步怎么办?刷新一下页面,平台会自动保存进度的。
课后测验能自动做吗?不行,得自己看题目。
# 结束语
周口市专业技术人员继续教育平台是周口地区专技人员每年都要用的,老李之前为这个课愁了好几天,用了脚本之后终于不用熬夜刷课了。河南这边的继续教育平台说实话体验都差不多,视频长、不自动跳、容易掉线,脚本能帮你解决大部分问题。
# 核心代码
(function() {
'use strict';
const CONFIG = {
siteDomain: 'zkzj.jxjyedu.org.cn',
checkInterval: 2900,
switchDelay: 3600,
activityGap: 11000,
doneThreshold: 91,
storageKey: 'zkzj_auto_state'
};
let state = {
running: false,
doneCount: 0,
speed: 1.0,
lastAction: Date.now(),
retryCount: 0
};
function log(msg) {
console.log(`[周口继续教育] ${msg}`);
}
function loadState() {
const saved = localStorage.getItem(CONFIG.storageKey);
if (saved) {
try {
const data = JSON.parse(saved);
state.running = data.on !== false;
} catch (e) {
state.running = true;
}
} else {
state.running = true;
}
}
function saveState() {
localStorage.setItem(CONFIG.storageKey, JSON.stringify({
on: state.running,
speed: state.speed
}));
}
function startup() {
loadState();
if (state.running) {
log('周口市专业技术人员继续教育自动学习已启动');
beginCheck();
}
buildUI();
}
function getVideo() {
const sels = [
'video',
'#myVideo video',
'.video-box video',
'.course-video video',
'.video-js video',
'.vjs-tech',
'video.player-video'
];
for (const sel of sels) {
const el = document.querySelector(sel);
if (el && el.duration > 0 && el.offsetParent !== null) {
return el;
}
}
return null;
}
function getVideoWrap() {
const wraps = [
'#myVideo',
'.video-box',
'.course-player',
'.video-container',
'.player-wrap',
'.vjs-container'
];
for (const sel of wraps) {
const el = document.querySelector(sel);
if (el) return el;
}
return document.body;
}
function calcProgress(vid) {
if (!vid || !vid.duration) return 0;
return (vid.currentTime / vid.duration) * 100;
}
function tryPlay(vid) {
if (!vid) return false;
try {
if (vid.paused) {
const p = vid.play();
if (p && p.catch) {
p.catch(() => {
vid.muted = true;
vid.play().catch(() => {});
});
}
}
return true;
} catch (e) {
return false;
}
}
function applySpeed(vid, spd) {
if (!vid) return;
try {
vid.playbackRate = spd;
state.speed = spd;
log(`倍速已设为 ${spd}x`);
} catch (e) {
log(`倍速设置失败`);
}
}
function antiIdle() {
const now = Date.now();
if (now - state.lastAction > CONFIG.activityGap) {
const wrap = getVideoWrap();
const rect = wrap.getBoundingClientRect();
const rx = rect.left + Math.random() * rect.width;
const ry = rect.top + Math.random() * rect.height;
const mv = new MouseEvent('mousemove', {
clientX: rx, clientY: ry, bubbles: true
});
document.dispatchEvent(mv);
setTimeout(() => {
const ck = new MouseEvent('click', {
clientX: rx, clientY: ry, bubbles: true
});
document.dispatchEvent(ck);
}, 600);
state.lastAction = now;
log('已模拟操作,防止掉线');
}
}
function findNext() {
const sels = ['.next-btn', '.btn-next', '.next-chapter', '[class*="next"]'];
for (const sel of sels) {
const btns = document.querySelectorAll(sel);
for (const btn of btns) {
if (btn.offsetParent !== null && !btn.disabled) {
return btn;
}
}
}
return null;
}
function getCourseList() {
return document.querySelectorAll(
'.course-item, .chapter-item, .lesson-item, .section-item, .catalog-item'
);
}
function isComplete(item) {
return item.querySelector('.status-done, .finished, .completed, .done-icon') !== null;
}
function isActive(item) {
return item.classList.contains('active') || item.classList.contains('current');
}
function goNext() {
const btn = findNext();
if (btn) {
btn.click();
state.doneCount++;
log(`点击下一节,已完成 ${state.doneCount} 节`);
setTimeout(beginCheck, CONFIG.switchDelay);
return;
}
const items = getCourseList();
let found = false;
for (const item of items) {
const done = isComplete(item);
const active = isActive(item);
if (active) { found = true; continue; }
if (found && !done) {
item.click();
state.doneCount++;
log('已跳到下一未完成章节');
setTimeout(beginCheck, CONFIG.switchDelay);
return;
}
}
log('全部完成或未找到更多课程');
}
function checkLoop() {
const vid = getVideo();
if (!vid) {
state.retryCount++;
if (state.retryCount > 20) {
log('多次未找到视频,请检查页面');
return;
}
setTimeout(checkLoop, CONFIG.checkInterval);
return;
}
state.retryCount = 0;
if (vid.paused && state.running) {
tryPlay(vid);
}
const prog = calcProgress(vid);
if (prog >= CONFIG.doneThreshold || vid.ended) {
log(`进度 ${prog.toFixed(1)}%,准备切换`);
goNext();
return;
}
antiIdle();
setTimeout(checkLoop, CONFIG.checkInterval);
}
function beginCheck() {
if (!state.running) return;
setTimeout(checkLoop, 2200);
}
function pauseAll() {
state.running = false;
saveState();
log('已暂停');
refreshUI();
}
function resumeAll() {
state.running = true;
saveState();
log('已恢复');
beginCheck();
refreshUI();
}
function toggleAll() {
state.running ? pauseAll() : resumeAll();
}
function setSpd(spd) {
const vid = getVideo();
if (vid) applySpeed(vid, spd);
state.speed = spd;
refreshUI();
}
function refreshUI() {
const s = document.getElementById('zkzj_st');
const c = document.getElementById('zkzj_ct');
const r = document.getElementById('zkzj_sp');
if (s) s.textContent = state.running ? '运行中' : '已暂停';
if (c) c.textContent = state.doneCount;
if (r) r.textContent = state.speed;
}
function buildUI() {
const old = document.getElementById('zkzj_panel');
if (old) return;
const panel = document.createElement('div');
panel.id = 'zkzj_panel';
panel.style.cssText = `
position:fixed;top:180px;right:20px;
background:linear-gradient(135deg,#e65100 0%,#ff8f00 100%);
color:#fff;padding:14px 16px;border-radius:10px;
box-shadow:0 3px 12px rgba(0,0,0,0.25);
z-index:999999;font-size:13px;min-width:170px;
`;
panel.innerHTML = `
<div style="font-weight:bold;margin-bottom:10px;font-size:14px;">
周口继续教育自动学习
</div>
<div style="margin-bottom:6px;">状态: <span id="zkzj_st">${state.running ? '运行中' : '已暂停'}</span></div>
<div style="margin-bottom:6px;">已完成: <span id="zkzj_ct">${state.doneCount}</span> 节</div>
<div style="margin-bottom:10px;">倍速: <span id="zkzj_sp">${state.speed}</span>x</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<button onclick="window.togZkzj()" style="
padding:5px 10px;border:none;border-radius:5px;
cursor:pointer;background:rgba(255,255,255,0.25);
color:#fff;font-size:12px;
">${state.running ? '暂停' : '开始'}</button>
<button onclick="window.spdZkzj(1.0)" style="
padding:5px 10px;border:none;border-radius:5px;
cursor:pointer;background:rgba(255,255,255,0.25);
color:#fff;font-size:12px;
">1x</button>
<button onclick="window.spdZkzj(1.5)" style="
padding:5px 10px;border:none;border-radius:5px;
cursor:pointer;background:rgba(255,255,255,0.25);
color:#fff;font-size:12px;
">1.5x</button>
<button onclick="window.spdZkzj(2.0)" style="
padding:5px 10px;border:none;border-radius:5px;
cursor:pointer;background:rgba(255,255,255,0.25);
color:#fff;font-size:12px;
">2x</button>
</div>
`;
document.body.appendChild(panel);
window.togZkzj = toggleAll;
window.spdZkzj = setSpd;
setInterval(refreshUI, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startup);
} else {
startup();
}
})();