网课自动播放下一节,真正的难点不在「播放」,而在「下一节」这三个字被网页写得有多松
昨天夜里十一点五十七分,我坐在书桌前,把一门 32 讲的《数据结构与算法》网课的第十四讲点开,然后打开浏览器的开发者工具,准备验证一个已经写了三天的脚本。事情的起因很简单:我同时在跟两门课,一门在教务平台,一门在第三方网校,平台的播放器在讲完一节之后会弹一个「继续学习」的浅蓝色按钮,位置固定在右下角,距离底部大约 48 像素,而第三方网校干脆什么都不弹,只把进度条停在 100% 的位置,像一块沉默的石头。我想要的自动化,是让这两家都乖乖往下走,而不是我在第三十次点击之后开始怀疑自己的耐心。
第一个版本我写得非常朴素:监听 video 元素的 ended 事件,然后在回调里去找页面上所有 a 标签,用正则匹配「下一节」「下一集」「继续学习」这几个词,命中就 click。这段代码在我手上跑了不到两分钟就被自己否掉了,原因也很好笑——第三方网校的播放器是把整个播放界面塞进一个 iframe 的,而 iframe 的 src 里带着一串每次刷新都会变的 token。我在 49tk.cc图库下载安装 那篇旧笔记里见过类似的结构描述,当时只当是别人的技术八卦,轮到自己才明白为什么那位作者用了「绕」这个字。绕不过去,就只能换监听的对象:不在外层找按钮,而是进到 iframe 内部去监听播放器自己派发的 customEvent,名字叫 lecture-complete,带一个 detail 字段,里面是下一讲的 id。
第二件事是标签页休眠。我把脚本挂上去之后,前五讲顺顺利利,第六讲卡住。我当时以为是网络,刷新之后重放,还是卡。后来我盯着 console 的日志时间戳看,发现卡住的那一分钟里,requestAnimationFrame 一次都没有触发,setInterval 也被拉长到了 60 秒一次——浏览器把不活跃的标签页降频了。我在 Chrome 138 上复现了三次,又在 Firefox 141 上复现了一次,两家表现还不一样:Chrome 会把后台标签页的定时器压到最低一分钟,Firefox 则是先冻结再恢复。解决方式不优雅但有效,我在页面里插入了一段几乎无声的 Web Audio 振荡器,频率 50 赫兹、音量 0.0001,让标签页保持「正在播放音频」的状态,降频就消失了。这个方法是我在 6996新ip登录入口1 的一篇归档讨论里翻到的,作者没有署名,只在末尾写了一句「不保证合规」,我照做,也照抄这句提醒。
第三个坑最隐蔽,也最值得写下来。脚本终于能跨节了,但我发现它偶尔会「跳过」一节:比如播完第 9 讲,直接进了第 11 讲。我把日志按毫秒打印出来,才发现播放器在结束时先派发一次 lecture-complete,300 毫秒后又派发一次,第二次的 detail 字段里下一讲的 id 是空的——它其实是在通知 UI 刷新,被我的正则误判成了「继续」。于是我加了一个很土的去重:记录上一次处理的 id,如果 detail.id 为空或者与上次相同,就丢弃。改完之后连跑 32 讲,一次没跳。这个细节我在 浏览器打开免费网站 那边的实测帖里也看到过类似描述,作者用的是「指纹」这个词,我觉得挺准确,因为它确实是靠事件内容本身来识别,而不是靠时间窗口。
写到这里,我得说一句可能不太受欢迎的个人观点:我并不认为这类自动播放脚本适合被做成「一键全自动」的成品分发给所有人。原因不是道德,而是工程上的诚实——每一家网课平台的播放器事件模型、iframe 结构、节次命名都不同,一个能跑通的脚本往往只在写它的那个人当时的那个版本上成立。把它包装成通用工具,等于把一堆「不保证」塞给用户。我自己的做法是留一个很小的配置块,把选择器、事件名、去重规则写成三个数组,改版的时候改数组,不改逻辑。这样至少诚实,改版了我自己也知道该从哪一行开始看。
最后补充一个实操细节,关于「恢复」。第二天下午我把脚本临时关掉,想手动点一次,结果发现播放器在长时间不动之后会进入一种半睡眠状态,点击「下一节」按钮没有反应,得先把视频点一下、让控件浮出来,再点按钮才行。这个行为在 uc浏览器网站快速入口 相关的讨论里有人叫它「懒唤醒」,也有人叫「二次激活」,名字不重要,重要的是脚本里最好先派发一次 mousemove,再等 200 毫秒去点按钮。我照做了,卡顿消失。如果你也在 qq无法打开qq浏览器 那种环境里跑过类似的东西,大概会同意我这句话:自动化的难点,八成都在这类看起来不值一提的两百毫秒里。
整件事收尾的时候是凌晨一点四十分。我把最后一节的进度条拖回 00:00,让它自己往下走完,然后关掉屏幕,去厨房续了半杯白开水。不激动,只是觉得踏实——一个具体的问题,被一个具体的方法解掉了,中间踩的三个坑都留下了记录。这大概就是我写这个站的原因。