尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
学习通刷课脚本全解析:Selenium自动化播放与FontTools字体解密实战
简介一套基于 Python 的学习通自动刷课脚本源码面向希望借助自动化完成平台课程任务、减少重复点击的学习者也适合想在 Selenium 浏览器操作和 fonttools 字体处理上做实践参考的 Python 开发者。资源包共 27 个文件、约 2.41MB核心为 17 个 Python 脚本按登录、IFrame 定位、任务检测、答题识别、章节切换、完成判断等模块独立拆分另有 pkl/json 字体映射数据、说明文档和界面图片便于厘清脚本运行逻辑与二次开发。项目还细分了事件检测、播放按钮识别、题目解析等实现能帮助初学者理解浏览器自动化脚本从页面元素定位到状态判断的完整流程。整体功能模块切割清晰可抽取登录或答题识别等某一段单独调试方便改造成其他在线学习场景的自动化脚本。目前已有 8096 人学习浏览作为可直接调试的完整源码包是一份兼具工具价值与自动化学习意义的脚本参考。1. 学习通刷课脚本selenium 和 fonttools 到底在替你解决哪两件难事课程积压、视频又长又拖的人多少都动过“弄个学习通刷课脚本”的念头。标题里两个包分工很清晰selenium 驱动真实浏览器去打开课程页、定位章节、触发播放、点掉弹窗把整个手动学习流程替换成自动化操作fonttools 去解析课件里加密的 woff 字体把页面中被映射到 Unicode 私有区的乱码字符还原成可读文本方便脚本做关键词匹配和内容整理。它解决的不是一个难题而是两件事让视频自己按正常节奏播完让课件文字能当成普通文本处理。适合谁适合已经选了这门课、愿意花一个晚上把重复劳动固化成代码的人也适合需要批量整理课件文字的人。前提很清楚——只处理你自己账号下已授权的课程内容把“自动点击”当成对手动操作的替代而不是绕过平台规则的捷径。2. 先看机制再看代码学习通网页端凭什么判定你“在学”fonttools 又为什么被挂进来2.1 学习通网页端课程页的播放逻辑video 标签、心跳上报与“已完成”判定学习通网页端课程视频的播放核心是 HTML5 的 video 标签早期 Flash 播放器基本被主流浏览器淘汰。正因为是标准 video 标签selenium 才能用execute_script直接读取currentTime、duration、paused、ended这些属性也正因为这些属性可以靠 JS 修改很多人才会冒出“直接把 currentTime 调到结尾不就行了”的想法。真这么做大概率章节还是灰色。平台端判定一场学习有没有效不看你截图而看页面前端有没有持续上报心跳播放器里的 JavaScript 每隔一段时间把当前播放时间点、用户停留时长、章节 ID 等信息组装成请求发给后端。所谓“刷课成功”本质是让这串心跳看起来像一个真人从开头看到了结尾。所以脚本的责任不是“跳到结尾”而是“模拟播放并等待结束”结束之后还要给页面留几秒上报余量。这也是第四章状态机部分要处理的核心问题。另一个容易被忽略的点有些课程视频被拆成多个片段有的页面里同时存在试听视频和正式视频。脚本如果只抓第一个 video可能把预览视频当成正课播结果正课进度原地不动。先理清这个页面里到底有几个播放器、哪个是当前章节真正要播的比急着写 play 命令更值得花时间。2.2 fonttools 的真实用途把页面里的私有区乱码还原成可读文本直接用 requests 拿学习通章节页的 HTML经常会发现页面文本里混杂着一堆像输入法特殊符号区的字符比如出现在私有区的\ue900、\uf62c。这不是普通乱码而是字体加密课件用自定义字体把常见汉字重新编码页面能正常显示是因为浏览器加载了对应字体文件并做了映射但 HTML 文本本身是一串被替换过的码位。在这种机制下脚本里的文本匹配、题干提取、关键词检索都会失效。fonttools包名fonttools导入路径是fontTools的作用就是解析这些 woff/ttf 文件读取 cmap 表把私有区码位映射回真实字符。要注意它不能一套映射通吃所有课程每个课件批次可能用不同字体解析后必须回到页面里做一次校验具体做法在 4.4 节会给出代码。这里也要划清边界fonttools 处理的是“字符映射”不是“图片识别”。如果课件直接把文字渲染成图片那就得上 OCR那是另一套完全不同的方案了。2.3 选型理由requests 为什么刷不动学习通selenium 为什么够用requests 能拿到请求响应但拿不到浏览器渲染后的 DOM不能执行 JavaScript也模拟不了播放器上报心跳所需的上下文环境。纯 requests 刷视频需要逆向前端签名、维护加密参数学习通页面稍微改版就要重新逆向维护成本远高于收益。观察同类项目会发现最后都滑向 selenium不是大家不会 requests而是真实浏览器才是门槛最低、最抗改版的稳定路径。selenium 的另一个好处是登录态管理简单手动登录一次cookie 存本地下次启动恢复即可不需要再造登录接口的签名。缺点也很明确浏览器自动化特征明显平台可以做 WebDriver 检测运行速度比接口调用慢headless 模式下 video 播放经常不正常。这几个痛点留到第五章逐个给现场。至于 Playwright 或 pyppeteer也能做同样的事只是标题既然锁定 selenium本文就按 selenium 这套来讲。3. 环境搭建与最小登录脚本从 pip 报错到浏览器驱动匹配3.1 安装 Python 依赖为什么建议用 python -m pip网上搜“python 安装”的热门问题里相当一部分是 Windows 下pip命令找不到。原因通常是 Python 装好了但 Scripts 目录没进 PATH或者系统里有多个 Python 版本pip 指向了另一个解释器。这类问题别急着改环境变量统一用python -m pip可以绕开大部分 PATH 错乱python --version python -m pip install fonttools selenium python -m pip list | findstr /i selenium fonttools第一条命令确认解释器版本第二条安装两个核心包第三条确认安装结果。Linux/macOS 下把python换成python3、findstr换成grep即可。这里顺带说明包名是fonttools代码里导入时才写from fontTools.ttLib import TTFont大小写不一样写错会直接 ImportError。如果当前用户没有权限写进全局目录可以加--user参数装到当前用户的 site-packages 里。3.2 selenium 驱动版本匹配Chrome、Edge 与 webdriver-manager 的取舍selenium 需要对应浏览器的驱动Chrome 用 chromedriverEdge 用 msedgedriver。驱动和浏览器版本差太多启动就会直接报 session 创建失败。常见选择有三类方式命令/配置特点手动下载驱动下载后放进 PATH 或写死 executable_path最稳浏览器升级时要手动换webdriver-managerpython -m pip install webdriver-manager自动匹配当前浏览器版本Selenium ManagerSelenium 4.6 起内置自动管理免配置但部分网络环境下自动下载容易失败我一般习惯用 webdriver-manager它能把“浏览器升级后驱动不匹配”这个坑自动填上。示例from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)这段代码先通过 ChromeDriverManager 拿到与本机 Chrome 版本匹配的驱动路径再构造 Service 传给 Chrome。如果用的是 Edge把导入路径换成webdriver_manager.microsoft里的EdgeChromiumDriverManager逻辑完全一样。这样就不需要写死 executable_path以后浏览器升级也不用动这行代码。注意自动下载依赖外网 CDN跑不动时直接手动下载驱动、放到项目目录再把 service 指向本地文件即可。3.3 能跑通的最小登录脚本手动登录、保存 cookie、维持会话刷课脚本最麻烦的不是播放是登录。学习通的登录流程里带验证码和扫码自动化成本高常见做法是“半自动”第一次打开浏览器让你手动登录保存 cookie以后复用。最小脚本如下import pickle from selenium import webdriver from selenium.webdriver.chrome.options import Options opts Options() opts.add_argument(--window-size1280,800) # 去掉自动化控制条减少被前端识别的特征 opts.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(optionsopts) driver.get(https://www.chaoxing.com/) input(请在浏览器里完成登录然后回到终端按回车继续…) with open(chaoxing.pkl, wb) as f: pickle.dump(driver.get_cookies(), f) print(cookies saved:, len(driver.get_cookies()))代码逻辑很直白启动浏览器打开学习通首页input()阻塞脚本等人手动登录登录完成后把当前浏览器的 cookies 序列化到本地 pickle 文件。这里有个坑只看 cookie 数量不等于登录成功学习通有多个子域名有些 cookie 只在特定域名下生效。所以保存前先在浏览器里进一次课程列表页确认登录态完整。下次启动先访问一次域名再注入 cookies因为 selenium 不允许在未访问对应域名时直接add_cookiedriver.get(https://www.chaoxing.com/) with open(chaoxing.pkl, rb) as f: cookies pickle.load(f) for c in cookies: driver.add_cookie(c) driver.refresh()运行后立刻访问课程列表页验证登录态。cookie 过期后删掉本地 pkl 重新手动登录一次即可。这个阶段不要加花活登录通路跑通后面的脚本才有意义。4. 从登录到刷完一个视频课程定位、自动播放与防掉线循环4.1 定位课程和章节选择器怎么写才能扛住改版登录后进入课程列表常见元素是带courseId参数的链接。定位课程最稳妥的办法不是用绝对 xpath而是先缩小范围再过滤文本from selenium.webdriver.common.by import By def click_course_by_name(driver, name): links driver.find_elements(By.XPATH, //a[contains(href, courseId)]) for link in links: if name in link.get_attribute(innerText): driver.execute_script(arguments[0].click(), link) return True return False这里用contains(href, courseId)圈出可能是课程入口的链接再用页面文本匹配课程名。很多课程卡片本身嵌在 iframe 里执行这个函数前要确认已经切到对应 frame否则find_elements只能看到外层空壳。定位不到时用开发者工具右键元素看父级 iframe比盲改 xpath 快得多。还有一个小技巧课程名匹配用“包含”而不是“等于”因为页面文本里可能掺着空格或特殊标记。进入章节列表后章节链接一般也带章节 ID。我常用同样的方式过滤先找到包含“章节测试”“视频”字样的条目再点进当前需要播放的章节。这里最容易翻车的点是“默认展开的是上一次学习的章节”而不是课程第一章节。如果脚本每次都从第一个章节开始可能会打乱你的学习记录所以定位时有记忆上次位置的能力很重要。4.2 视频播放的完整状态机进入 iframe、触发 play、等待 ended章节页的视频播放器通常套在多层 iframe 里先切进去再找 video顺序不能反from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def enter_player_frame(driver): for _ in range(3): frames driver.find_elements(By.TAG_NAME, iframe) if frames: driver.switch_to.frame(frames[0]) else: break driver.switch_to.default_content() enter_player_frame(driver) video WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.TAG_NAME, video)) )切入后如果视频没有自动播放用 JS 触发而不是去点播放按钮driver.execute_script(arguments[0].play(), video) time.sleep(2)点播放按钮容易被播放器遮罩或自定义控件拦截play()是原生方法绕开了 UI 层。注意enter_player_frame每次都从default_content开始逐层切入避免之前切到内层 frame 导致后续查找失效。如果页面结构固定也可以写死切入第几层但逐层找 iframe 的写法更抗改版。等待视频结束可以轮询 video 的三个状态import time def wait_video_end(driver, video, timeout3600): start time.time() while time.time() - start timeout: state driver.execute_script( return {ended: arguments[0].ended, t: arguments[0].currentTime, d: arguments[0].duration}, video, ) if state[ended] or (state[d] and state[t] state[d] - 1): return state time.sleep(3) raise TimeoutError(video 播放超时)这个循环每 3 秒读一次播放状态t d - 1是兼容某些视频不到ended就停住的情况误差控制在 1 秒内。这里还能顺带做“进度不变检测”如果连续多次t没有变化说明被弹窗拦住需要调用下一节的弹窗清理函数。把“播放进度”和“心跳”分开看是刷课脚本不翻车的关键。4.3 弹窗轮询与挂机保活用一条循环兜住所有“你还在吗”视频播放过程中学习通会不定期弹出“已暂停请点击继续”之类的提示弹窗不点进度就原地不动。这类弹窗不是严格定时出现的只能靠轮询点击def dismiss_popups(driver, keywords(继续学习, 点击继续, 知道了)): 点掉播放期间的弹窗找不到按钮就忽略。 for keyword in keywords: try: btn driver.find_element( By.XPATH, f//*[contains(normalize-space(.), {keyword})] ) btn.click() time.sleep(1) return True except Exception: continue return Falsenormalize-space(.)会折叠元素文本里的空格和换行避免文本中间有空白导致匹配失败。调用时机放在wait_video_end的循环里每轮检测完进度后调用一次dismiss_popups。这样弹窗无论出现在第几分钟都能在几秒内被点掉。注意点击“继续学习”后播放器通常需要重新触发一次 play否则提示框虽然没了视频仍然 pause。所以弹窗处理后要再补一句 JS 判断paused仍为 True 就调一次play()。轮询频率不需要太密3~5 秒一次足够太频繁反而容易点错页面里的其他元素。另外弹窗按钮的 DOM 每次可能重建别缓存元素每轮现查现点。4.4 用 fonttools 解析课件 woff 字体把私有区字符还原成可读文本当页面文本混入大量私有区字符时脚本拿到的字符串没法用于匹配。常见做法是把 CSS 里内嵌的 base64 woff 数据取出来用 fonttools 建一张码位映射表import base64 import io import re from fontTools.ttLib import TTFont def parse_base64_woff(css_text): match re.search(rurl\(\s*[\]?data:font/woff[^,]*;base64,([^)\\s]), css_text) if not match: return None return base64.b64decode(match.group(1)) def build_char_map(woff_bytes): font TTFont(io.BytesIO(woff_bytes)) char_map {} for table in font[cmap].tables: for code, glyph_name in table.cmap.items(): # 常见字形名是 uniXXXX 或 uXXXXcode 是页面里的私有区字符 if glyph_name.startswith(uni): char_map[chr(code)] chr(int(glyph_name[3:], 16)) elif glyph_name.startswith(u): char_map[chr(code)] chr(int(glyph_name[1:], 16)) return char_map拿到char_map之后把页面文本里的每个私有区字符替换成映射后的真实字符就能继续做关键词匹配。两个前提要注意第一字体文件要下载当前章节实际加载的那份页面里可能有多批字体第二如果字形名是glyph00001这种无意义命名说明字体做过子集化这类要靠 OCR 识别字形轮廓属于另一套方案。fonttools 解决的是“映射已知但文本不可读”的常见场景不是万能解药。解析完可以用页面标题做一次校准找一个浏览器里显示正常的中文标题看映射表能不能把它还原对。5. 学习通刷课脚本避坑指南进度不走、弹窗拦截和 selenium 被检测的 5 个现场5.1 视频播完了章节还是灰色差在最后一次心跳上报现象脚本日志显示视频已经ended也调用了dismiss_popups但回到学习通章节列表状态仍是未完成。原因播放结束后页面立即切章或刷新前端最后一段心跳还没发出去后端没收到“看完”的信号。解决wait_video_end返回后不要马上进入下一章先停 8~10 秒让播放器把最后的进度上报完再刷新一次章节列表确认状态。这个“播完后静置”的习惯能减少一大批进度丢失尤其适合网络延迟偏高的场景。5.2 播放途中弹出“点击继续”脚本却没反应现象脚本进程还在跑但视频停了日志里currentTime连续多次不变。原因弹窗出现在某个新增 iframe 或遮罩里之前保存的元素引用已经失效或者dismiss_popups的匹配词没覆盖当次文案。解决把弹窗检测放进进度的轮询循环里每次读状态前先执行一次dismiss_popups匹配词不要只写“继续学习”把“点击继续”“知道了”“重新播放”都放进去点完弹窗后检查paused为 True 就再补一次play()。记住弹窗按钮的 DOM 每次可能重建别缓存元素每次现查现点。5.3 被 WebDriver 特征检测拦住出现滑块或拒绝访问现象正常手动访问没问题脚本一打开页面没过多久就弹滑块验证甚至直接提示访问异常。原因navigator.webdriver标识、自动化控制条、窗口尺寸过于规整这些特征叠加在一起被前端识别出来。解决启动参数里加excludeSwitches手动设置窗口尺寸不要开 headless如果还不够可以引入undetected_chromedriverimport undetected_chromedriver as uc driver uc.Chrome()同样的登录、定位、播放流程不用改。需要强调的是过度隐身配置反而更反常一个普通窗口在正常播放才是长期运行更稳妥的状态。另外检测不一定发生在进入页面时也可能发生在某个章节切换动作之后所以日志里多留一步“当前 URL”会方便事后定位是被哪一步拦住的。5.4 headless 模式下视频不播放或进度不涨现象为了省资源在服务器上跑 headless Chrome课程页能打开但 video 一直停在 0 秒或直接黑屏。原因headless 下视频解码和音频输出策略与正常浏览器不同播放器拿不到足够的媒体回放能力play()返回的 Promise 可能一直不落定。解决不要为了“低调”开 headless用一个最小化的有头窗口配合桌面环境运行。如果必须 headless常见做法是加--mute-audio并显式调用 play但仍不能保证稳定。这不算玄学而是浏览器媒体栈在无头模式下的设计取舍。5.5 fonttools 映射出来的文本仍然是乱码现象解析了 woff 并做了替换页面里的段落还是半对半错。原因页面加载了多批字体脚本只解析了其中一份或者字形命名不是uniXXXXcmap 信息不足以还原。解决先看 CSS 里有几个字体源逐个解析、合并映射表后再做替换再拿页面里的已知标题文案反查映射是否正确。比如某个标题在浏览器里固定显示为“第一章”在 HTML 里对应的私有区字符就可以当作校准样本。样本校准能立刻分辨是映射表不全还是字体解析错误。6. 脚本刷完怎么验收、怎么续跑日志、任务列表和中断恢复6.1 用日志记录心跳验证脚本真的在带进度验收不是看脚本跑完而是看学习通的章节状态。我的习惯是每轮循环写一条结构化日志import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(message)s) def log_progress(chapter, state): logging.info( chapter%s endpoint%.0f current%.0f pause%s, chapter, state[d], state[t], state[paused], )跑完后从日志里能看到每个视频是否一路推进也能对比学习通页面的完成状态来确认“真的刷完了”。如果某章日志里current长时间不动说明踩了第五章里的某个坑直接重跑那一章就行不需要全量重来。6.2 给脚本加“断点续跑”和任务队列把所有章节做成列表完成一个就写进本地 JSON下次启动先读列表跳过已完成章节。这个机制是血泪教训换来的网络波动、弹窗漏点、平台临时维护都会让长任务中断没有断点续跑就只能从头再来。任务列表加日志就能在第二天早晨看清昨晚哪个任务在哪一步翻车只修那一处而不是删掉整个进度重刷。技术含量不高但它是这套脚本能真正落地长期使用的保障。我自己的习惯是把日志文件名带上当天日期跑完再看一眼章节状态心里才有底。刷课脚本的本质是“替你把可控的重复劳动自动化”写代码时把失败可恢复排在第一位比一时跑通更重要。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Obsidian图文写作流水线:从Workflow到Skill,Token成本直降七成

Obsidian图文写作流水线:从Workflow到Skill,Token成本直降七成

1. 从Workflow到Skill:一次被Token账单逼出来的架构调整去年下半年我开始用Obsidian搭自己的图文写作流水线,核心诉求很简单:把零散的素材、大纲、配图说明、发布文案串成一条自动化的链路。最开始我走的是Workflow路线——用插件把多个动作串…

📅 2026/10/7 15:03:15
零依赖纯文本知识库:用文件夹+Git搭建永不过期的个人笔记系统

零依赖纯文本知识库:用文件夹+Git搭建永不过期的个人笔记系统

caveman 是我最近一直挂在嘴边的一个小项目代号,外号叫“穴居人方案”。圈子里偶尔看到叫 caveman 的工具或开源项目,思路大多差不多:把知识管理这件事打回原形,不用花哨的 App,不用云端数据库,就用文件夹、…

📅 2026/10/7 15:03:15
Obsidian插件Superpowers详解:从安装配置到高效文本编辑技巧

Obsidian插件Superpowers详解:从安装配置到高效文本编辑技巧

1. 为什么 Obsidian 玩家都在聊 Superpowers如果你是个 Obsidian 重度用户,最近逛社区或插件市场时应该会频繁撞见“Superpowers”这个名字。它不是一个让你在笔记里写代码开外挂的神秘工具,而是一个把 Obsidian 原生的文本编辑、搜索、滚动、书签这些基…

📅 2026/10/7 15:03:15
MORE NEWS

更多资讯

📰

观察级ROV机械手臂选型与实操:自由度、驱动及维护排障

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

YOLOv5 6.0吸烟检测实战:从数据集配置到边缘部署全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

ADE Explorer实战指南:从Spectre仿真配置到Monte Carlo与收敛排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

控制即推理:从贝叶斯推断看强化学习中的最大熵与SAC

经常有人问我,为什么近几年强化学习里到处都是 (\pi(a|s)\propto\exp(Q(s,a)/\alpha)) 这种写法,还有SAC里那个自动调节的温度系数到底从哪来的。追根溯源,答案基本都落在同一个理论框架上——Control as Inference,翻译过来就是“…

📰

ponytail skill 插件使用指南:从零搭建自动化工作流

1. 从“ponytail”这个词说起:它到底是什么第一次看到“ponytail”这个词,大多数人脑子里蹦出来的画面是扎在脑后的一束马尾辫。但在技术圈和工具链语境里,它早就不是发型的意思了。最近一段时间,ponytail skill、ponytail 插件、…

📰

Allegro 17.2信号线组等长设计闭环全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬